diff --git a/AGENTS.md b/AGENTS.md index a3c8026..67993ab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,3 +15,5 @@ - TypeScript는 `strict` 모드 기준으로 작성한다. `any`는 사용하지 않고, `as` 타입 단언도 꼭 필요한 경우가 아니면 지양한다. - SCSS는 nesting 기준으로 작성하되, 컴포넌트 클래스는 BEM 스타일의 `&__` / `&--` 형태를 우선 사용한다. 같은 블록 안에서 하위 요소는 일관된 네이밍으로 묶는다. - Nuxt, Vue 관련 코드 수정이 필요하면 `vue-expert` 서브 에이전트를 우선 사용한다. + +@DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..f3a899a --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,881 @@ +--- +version: alpha +name: Notion +description: Notion presents itself as the all-in-one workspace through a confident, illustration-rich brand voice — anchored by a deep navy hero band ({colors.brand-navy}) decorated with brand-colored sticky-note dots and mesh wire illustrations, a signature purple pill primary CTA ({colors.primary}), and a rich palette of pastel-tinted feature cards that echo the colorful database properties of the live product. The system uses a Notion-Sans (Inter-based) typeface across every UI surface, anchors a 4-tier pricing comparison (Free / Plus / Business / Enterprise), and presents the live workspace UI mockup directly inside the hero band. Coverage spans homepage, Enterprise, Product AI, Product Agents, Startups, and Pricing surfaces. + +colors: + primary: "#5645d4" + primary-pressed: "#4534b3" + primary-deep: "#3a2a99" + on-primary: "#ffffff" + brand-navy: "#0a1530" + brand-navy-deep: "#070f24" + brand-navy-mid: "#1a2a52" + link-blue: "#0075de" + link-blue-pressed: "#005bab" + brand-orange: "#dd5b00" + brand-orange-deep: "#793400" + brand-pink: "#ff64c8" + brand-pink-deep: "#a02e6d" + brand-purple: "#7b3ff2" + brand-purple-300: "#d6b6f6" + brand-purple-800: "#391c57" + brand-teal: "#2a9d99" + brand-green: "#1aae39" + brand-yellow: "#f5d75e" + brand-brown: "#523410" + card-tint-peach: "#ffe8d4" + card-tint-rose: "#fde0ec" + card-tint-mint: "#d9f3e1" + card-tint-lavender: "#e6e0f5" + card-tint-sky: "#dcecfa" + card-tint-yellow: "#fef7d6" + card-tint-yellow-bold: "#f9e79f" + card-tint-cream: "#f8f5e8" + card-tint-gray: "#f0eeec" + canvas: "#ffffff" + surface: "#f6f5f4" + surface-soft: "#fafaf9" + hairline: "#e5e3df" + hairline-soft: "#ede9e4" + hairline-strong: "#c8c4be" + ink-deep: "#000000" + ink: "#1a1a1a" + charcoal: "#37352f" + slate: "#5d5b54" + steel: "#787671" + stone: "#a4a097" + muted: "#bbb8b1" + on-dark: "#ffffff" + on-dark-muted: "#a4a097" + semantic-success: "#1aae39" + semantic-warning: "#dd5b00" + semantic-error: "#e03131" + +typography: + hero-display: + fontFamily: Notion Sans + fontSize: 80px + fontWeight: 600 + lineHeight: 1.05 + letterSpacing: -2px + display-lg: + fontFamily: Notion Sans + fontSize: 56px + fontWeight: 600 + lineHeight: 1.10 + letterSpacing: -1px + heading-1: + fontFamily: Notion Sans + fontSize: 48px + fontWeight: 600 + lineHeight: 1.15 + letterSpacing: -0.5px + heading-2: + fontFamily: Notion Sans + fontSize: 36px + fontWeight: 600 + lineHeight: 1.20 + letterSpacing: -0.5px + heading-3: + fontFamily: Notion Sans + fontSize: 28px + fontWeight: 600 + lineHeight: 1.25 + heading-4: + fontFamily: Notion Sans + fontSize: 22px + fontWeight: 600 + lineHeight: 1.30 + heading-5: + fontFamily: Notion Sans + fontSize: 18px + fontWeight: 600 + lineHeight: 1.40 + subtitle: + fontFamily: Notion Sans + fontSize: 18px + fontWeight: 400 + lineHeight: 1.50 + body-md: + fontFamily: Notion Sans + fontSize: 16px + fontWeight: 400 + lineHeight: 1.55 + body-md-medium: + fontFamily: Notion Sans + fontSize: 16px + fontWeight: 500 + lineHeight: 1.55 + body-sm: + fontFamily: Notion Sans + fontSize: 14px + fontWeight: 400 + lineHeight: 1.50 + body-sm-medium: + fontFamily: Notion Sans + fontSize: 14px + fontWeight: 500 + lineHeight: 1.50 + caption: + fontFamily: Notion Sans + fontSize: 13px + fontWeight: 400 + lineHeight: 1.40 + caption-bold: + fontFamily: Notion Sans + fontSize: 13px + fontWeight: 600 + lineHeight: 1.40 + micro: + fontFamily: Notion Sans + fontSize: 12px + fontWeight: 500 + lineHeight: 1.40 + micro-uppercase: + fontFamily: Notion Sans + fontSize: 11px + fontWeight: 600 + lineHeight: 1.40 + letterSpacing: 1px + button-md: + fontFamily: Notion Sans + fontSize: 14px + fontWeight: 500 + lineHeight: 1.30 + +rounded: + xs: 4px + sm: 6px + md: 8px + lg: 12px + xl: 16px + xxl: 20px + xxxl: 24px + full: 9999px + +spacing: + xxs: 4px + xs: 8px + sm: 12px + md: 16px + lg: 20px + xl: 24px + xxl: 32px + xxxl: 40px + section-sm: 48px + section: 64px + section-lg: 96px + hero: 120px + +components: + button-primary: + backgroundColor: "{colors.primary}" + textColor: "{colors.on-primary}" + typography: "{typography.button-md}" + rounded: "{rounded.md}" + padding: "10px 18px" + button-primary-pressed: + backgroundColor: "{colors.primary-pressed}" + textColor: "{colors.on-primary}" + button-primary-disabled: + backgroundColor: "{colors.hairline}" + textColor: "{colors.muted}" + button-dark: + backgroundColor: "{colors.ink-deep}" + textColor: "{colors.on-dark}" + typography: "{typography.button-md}" + rounded: "{rounded.md}" + padding: "10px 18px" + button-secondary: + backgroundColor: "transparent" + textColor: "{colors.ink}" + typography: "{typography.button-md}" + rounded: "{rounded.md}" + padding: "10px 18px" + border: "1px solid {colors.hairline-strong}" + button-on-dark: + backgroundColor: "{colors.on-dark}" + textColor: "{colors.ink}" + typography: "{typography.button-md}" + rounded: "{rounded.md}" + padding: "10px 18px" + button-secondary-on-dark: + backgroundColor: "transparent" + textColor: "{colors.on-dark}" + typography: "{typography.button-md}" + rounded: "{rounded.md}" + padding: "10px 18px" + border: "1px solid {colors.on-dark-muted}" + button-ghost: + backgroundColor: "transparent" + textColor: "{colors.ink}" + typography: "{typography.button-md}" + rounded: "{rounded.sm}" + padding: "8px 12px" + button-link: + backgroundColor: "transparent" + textColor: "{colors.link-blue}" + typography: "{typography.body-sm-medium}" + padding: "0" + card-base: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "{spacing.xl}" + border: "1px solid {colors.hairline}" + card-feature: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + border: "1px solid {colors.hairline}" + card-feature-yellow-bold: + backgroundColor: "{colors.card-tint-yellow-bold}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-feature-peach: + backgroundColor: "{colors.card-tint-peach}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-feature-rose: + backgroundColor: "{colors.card-tint-rose}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-feature-mint: + backgroundColor: "{colors.card-tint-mint}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-feature-sky: + backgroundColor: "{colors.card-tint-sky}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-feature-lavender: + backgroundColor: "{colors.card-tint-lavender}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-feature-yellow: + backgroundColor: "{colors.card-tint-yellow}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-feature-cream: + backgroundColor: "{colors.card-tint-cream}" + textColor: "{colors.charcoal}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + card-agent-tile: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "{spacing.xl}" + border: "1px solid {colors.hairline}" + card-template: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "{spacing.lg}" + border: "1px solid {colors.hairline}" + card-startup-perk: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "{spacing.xl}" + border: "1px solid {colors.hairline}" + pricing-card: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + border: "1px solid {colors.hairline}" + pricing-card-featured: + backgroundColor: "{colors.surface}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + border: "2px solid {colors.primary}" + text-input: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + typography: "{typography.body-md}" + rounded: "{rounded.md}" + padding: "{spacing.sm} {spacing.md}" + border: "1px solid {colors.hairline-strong}" + height: 44px + text-input-focused: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + border: "2px solid {colors.primary}" + search-pill: + backgroundColor: "{colors.surface}" + textColor: "{colors.steel}" + typography: "{typography.body-md}" + rounded: "{rounded.md}" + padding: "{spacing.sm} {spacing.md}" + height: 44px + border: "1px solid {colors.hairline}" + pill-tab: + backgroundColor: "transparent" + textColor: "{colors.steel}" + typography: "{typography.body-sm-medium}" + rounded: "{rounded.full}" + padding: "{spacing.xs} {spacing.md}" + border: "1px solid {colors.hairline}" + pill-tab-active: + backgroundColor: "{colors.ink-deep}" + textColor: "{colors.on-dark}" + rounded: "{rounded.full}" + border: "1px solid {colors.ink-deep}" + segmented-tab: + backgroundColor: "transparent" + textColor: "{colors.steel}" + typography: "{typography.body-sm-medium}" + padding: "{spacing.sm} {spacing.md}" + border: "0 0 2px transparent solid" + segmented-tab-active: + backgroundColor: "transparent" + textColor: "{colors.ink}" + typography: "{typography.body-sm-medium}" + border: "0 0 2px {colors.ink} solid" + badge-purple: + backgroundColor: "{colors.primary}" + textColor: "{colors.on-primary}" + typography: "{typography.caption-bold}" + rounded: "{rounded.full}" + padding: "4px 10px" + badge-pink: + backgroundColor: "{colors.brand-pink}" + textColor: "{colors.on-primary}" + typography: "{typography.caption-bold}" + rounded: "{rounded.full}" + padding: "4px 10px" + badge-orange: + backgroundColor: "{colors.brand-orange}" + textColor: "{colors.on-primary}" + typography: "{typography.caption-bold}" + rounded: "{rounded.full}" + padding: "4px 10px" + badge-tag-purple: + backgroundColor: "{colors.card-tint-lavender}" + textColor: "{colors.brand-purple-800}" + typography: "{typography.caption-bold}" + rounded: "{rounded.sm}" + padding: "2px 8px" + badge-tag-orange: + backgroundColor: "{colors.card-tint-peach}" + textColor: "{colors.brand-orange-deep}" + typography: "{typography.caption-bold}" + rounded: "{rounded.sm}" + padding: "2px 8px" + badge-tag-green: + backgroundColor: "{colors.card-tint-mint}" + textColor: "{colors.brand-green}" + typography: "{typography.caption-bold}" + rounded: "{rounded.sm}" + padding: "2px 8px" + badge-popular: + backgroundColor: "{colors.primary}" + textColor: "{colors.on-primary}" + typography: "{typography.caption-bold}" + rounded: "{rounded.full}" + padding: "4px 10px" + promo-banner: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + typography: "{typography.body-sm-medium}" + padding: "{spacing.sm} {spacing.md}" + hero-band-dark: + backgroundColor: "{colors.brand-navy}" + textColor: "{colors.on-dark}" + rounded: "0" + padding: "{spacing.hero}" + workspace-mockup-card: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "0" + border: "1px solid {colors.hairline}" + shadow: "rgba(15, 15, 15, 0.2) 0px 24px 48px -8px" + cta-banner-light: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: "{spacing.section}" + comparison-table: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + typography: "{typography.body-sm}" + rounded: "{rounded.md}" + border: "1px solid {colors.hairline}" + comparison-row: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + padding: "{spacing.md} {spacing.lg}" + border: "0 0 1px {colors.hairline-soft} solid" + testimonial-card: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.lg}" + padding: "{spacing.xxl}" + border: "1px solid {colors.hairline}" + logo-wall-item: + backgroundColor: "transparent" + textColor: "{colors.steel}" + typography: "{typography.body-md-medium}" + padding: "{spacing.lg}" + faq-accordion-item: + backgroundColor: "{colors.canvas}" + rounded: "{rounded.md}" + padding: "{spacing.xl}" + border: "0 0 1px {colors.hairline} solid" + stat-row: + backgroundColor: "{colors.surface}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: "{spacing.section-sm}" + footer-region: + backgroundColor: "{colors.canvas}" + textColor: "{colors.charcoal}" + typography: "{typography.body-sm}" + padding: "{spacing.section} {spacing.xxl}" + border: "1px solid {colors.hairline}" + footer-link: + backgroundColor: "transparent" + textColor: "{colors.steel}" + typography: "{typography.body-sm}" + padding: "{spacing.xxs} 0" +--- + +## Overview + +Notion presents itself as the all-in-one workspace through a confident, illustration-rich brand voice. The homepage opens with **"Meet the night shift."** rendered centered over a deep navy hero band ({colors.brand-navy}), decorated with brand-colored sticky-note dots and mesh wire illustrations scattered around the headline. The signature **purple pill primary CTA** ({colors.primary}) "Get Notion free" sits at the visual center, paired with an outlined "Request a demo" secondary. Below the buttons, a real Notion workspace UI mockup card (the "Ramp HQ" kanban board) breaks out of the hero band with a deep diffuse drop shadow. + +Below the hero, the page cycles through a distinctive sequence of feature sections: a dense sticky-note "Keep work moving 24/7" panel with red/blue/green/purple/teal status icons; a **bold yellow** ({colors.card-tint-yellow-bold}) "Ask your on-demand assistants" banner card flanked by orange/rose/mint pastel feature tiles showing assistant UI mockups; and a "Bring all your work together" 3-column grid with brand-colored mockups (sky-blue tutorial card, light Notion calendar, brown/rust testimonial slate). The pricing page renders 4 tiers (Free / Plus / Business / Enterprise) horizontally with one tier featured (purple-bordered) and a dense feature comparison table running below. + +The system uses a Notion-Sans typeface (Inter-based) across every UI surface — humanist-geometric character that pairs naturally with the colorful illustrations. Buttons are `{rounded.md}` (8px) rectangles, NOT pills — distinguishing Notion's sober rectangular geometry from competitors that use pills universally. Cards use `{rounded.lg}` (12px) consistently. + +**Key Characteristics:** + +- Deep navy hero band ({colors.brand-navy}) with scattered sticky-note dots + mesh wire decorative illustrations +- **Signature purple pill** ({colors.primary}) primary CTA — Notion's recognizable "Get Notion free" button color +- Real Notion workspace UI mockup card embedded in the hero with deep drop shadow +- Bold yellow feature banner ({colors.card-tint-yellow-bold}) for high-emphasis content sections +- Pastel feature card palette (peach, rose, mint, lavender, sky, yellow) echoing the live product database properties +- Notion-Sans (Inter-based) across every UI surface +- 8px-rounded buttons (NOT pills), 12px-rounded cards — sober editorial geometry +- 4-tier pricing comparison with dense feature table +- Centered hero layout (different from the left-aligned norm of most B2B SaaS) + +## Colors + +> Source pages: notion.com/ (homepage), /enterprise, /product/ai, /product/agents, /startups, /pricing. Token coverage was identical across all six pages. + +### Brand & Primary + +- **Notion Purple** ({colors.primary}): Signature primary CTA color — the unmistakable "Get Notion free" pill button. Reserved for the dominant CTA only. +- **Purple Pressed** ({colors.primary-pressed}): Pressed-state variant +- **Purple Deep** ({colors.primary-deep}): Deeper variant for emphasis +- **Brand Navy** ({colors.brand-navy}): Hero band background — deep navy +- **Brand Navy Deep** ({colors.brand-navy-deep}): Deeper navy for promo banner +- **Brand Navy Mid** ({colors.brand-navy-mid}): Mid-spectrum navy +- **Link Blue** ({colors.link-blue}): Inline text link blue (NOT primary CTA) +- **Link Blue Pressed** ({colors.link-blue-pressed}): Pressed-state link blue + +### Brand Color Spectrum (echoes live product database properties) + +- **Brand Pink** ({colors.brand-pink}): Pink accent +- **Brand Pink Deep** ({colors.brand-pink-deep}): Deeper pink +- **Brand Orange** ({colors.brand-orange}): Orange accent +- **Brand Orange Deep** ({colors.brand-orange-deep}): Deeper orange-rust +- **Brand Purple** ({colors.brand-purple}): Purple accent variant +- **Brand Purple 300** ({colors.brand-purple-300}): Light purple +- **Brand Purple 800** ({colors.brand-purple-800}): Deep purple for tag text +- **Brand Teal** ({colors.brand-teal}): Teal accent +- **Brand Green** ({colors.brand-green}): Bright green +- **Brand Yellow** ({colors.brand-yellow}): Soft yellow +- **Brand Brown** ({colors.brand-brown}): Brand brown for "earthy" tints + +### Card Tints (Pastel Feature Card Backgrounds) + +- **Tint Peach** ({colors.card-tint-peach}): Pale peach +- **Tint Rose** ({colors.card-tint-rose}): Pale rose-pink +- **Tint Mint** ({colors.card-tint-mint}): Pale mint-green +- **Tint Lavender** ({colors.card-tint-lavender}): Pale lavender +- **Tint Sky** ({colors.card-tint-sky}): Pale sky-blue +- **Tint Yellow** ({colors.card-tint-yellow}): Pale yellow +- **Tint Yellow Bold** ({colors.card-tint-yellow-bold}): Bold yellow for high-emphasis feature banners ("Ask your on-demand assistants") +- **Tint Cream** ({colors.card-tint-cream}): Cream tint +- **Tint Gray** ({colors.card-tint-gray}): Neutral surface + +### Surface + +- **Canvas White** ({colors.canvas}): Page background and primary card surface +- **Surface** ({colors.surface}): Subtle section backgrounds, search-pill rest, featured pricing tier +- **Surface Soft** ({colors.surface-soft}): Quieter section divisions +- **Hairline** ({colors.hairline}): 1px borders and primary dividers +- **Hairline Soft** ({colors.hairline-soft}): Quieter dividers +- **Hairline Strong** ({colors.hairline-strong}): Stronger 1px border for inputs + +### Text + +- **Ink Deep** ({colors.ink-deep}): Pure black for emphasis +- **Ink** ({colors.ink}): Primary headlines and body text +- **Charcoal** ({colors.charcoal}): Body emphasis (Notion's signature warm-charcoal) +- **Slate** ({colors.slate}): Secondary text +- **Steel** ({colors.steel}): Tertiary, footer links +- **Stone** ({colors.stone}): Muted labels +- **Muted** ({colors.muted}): Disabled, placeholders +- **On Dark** ({colors.on-dark}): White text on dark surfaces +- **On Dark Muted** ({colors.on-dark-muted}): Reduced-opacity white + +### Semantic + +- **Success** ({colors.semantic-success}): Confirmation green +- **Warning** ({colors.semantic-warning}): Mid-priority alerts (orange) +- **Error** ({colors.semantic-error}): Validation errors (red) + +## Typography + +### Font Family + +**Notion Sans** (primary): Notion's custom Inter-based variable typeface. Fallbacks: Inter, -apple-system, system-ui, 'Segoe UI', Helvetica, sans-serif. Humanist-geometric character used across every UI surface. + +### Hierarchy + +| Token | Size | Weight | Line Height | Letter Spacing | Use | +| ----------------------------- | ---- | ------ | ----------- | -------------- | ---------------------------------------------- | +| `{typography.hero-display}` | 80px | 600 | 1.05 | -2px | Hero ("Meet the night shift") | +| `{typography.display-lg}` | 56px | 600 | 1.10 | -1px | Section openers | +| `{typography.heading-1}` | 48px | 600 | 1.15 | -0.5px | Page-level headlines ("Try for free") | +| `{typography.heading-2}` | 36px | 600 | 1.20 | -0.5px | Subsection headlines ("Keep work moving 24/7") | +| `{typography.heading-3}` | 28px | 600 | 1.25 | 0 | Card titles | +| `{typography.heading-4}` | 22px | 600 | 1.30 | 0 | Feature tile titles | +| `{typography.heading-5}` | 18px | 600 | 1.40 | 0 | FAQ questions | +| `{typography.subtitle}` | 18px | 400 | 1.50 | 0 | Hero subtitle | +| `{typography.body-md}` | 16px | 400 | 1.55 | 0 | Primary body text | +| `{typography.body-md-medium}` | 16px | 500 | 1.55 | 0 | Body emphasis | +| `{typography.body-sm}` | 14px | 400 | 1.50 | 0 | Secondary body | +| `{typography.body-sm-medium}` | 14px | 500 | 1.50 | 0 | Active sidebar, button labels | +| `{typography.caption-bold}` | 13px | 600 | 1.40 | 0 | Badge labels | +| `{typography.button-md}` | 14px | 500 | 1.30 | 0 | Button labels | + +### Principles + +- Tight hero leading (1.05) on 80px display +- Negative letter-spacing on display sizes (-2px to -0.5px) +- Generous body leading (1.55) for documentation readability +- 600 weight for headlines + 500 for buttons; 400 body + +## Layout + +### Spacing System + +- **Base unit**: 4px (8px primary increment) +- **Tokens**: `{spacing.xxs}` (4px) through `{spacing.hero}` (120px) +- **Section rhythm**: Marketing pages use `{spacing.section-lg}` (96px); pricing tightens to `{spacing.section}` (64px) + +### Grid & Container + +- 1280px max-width with 32px gutters +- Pricing: 4-tier card row at desktop with dense comparison table +- Homepage: centered hero with workspace mockup below buttons; alternating colorful feature card sections + +### Whitespace Philosophy + +Marketing surfaces use generous breathing room between feature card bands. Workspace mockup card on hero gets full-width treatment with deep drop shadow. + +## Elevation & Depth + +| Level | Treatment | Use | +| ---------- | ------------------------------------------- | -------------------------- | +| 0 (flat) | No shadow; `{colors.hairline}` border | Default cards, table rows | +| 1 (subtle) | `rgba(15, 15, 15, 0.04) 0px 1px 2px 0px` | Hover-elevated tiles | +| 2 (card) | `rgba(15, 15, 15, 0.08) 0px 4px 12px 0px` | Feature cards | +| 3 (mockup) | `rgba(15, 15, 15, 0.20) 0px 24px 48px -8px` | Hero workspace mockup card | +| 4 (modal) | `rgba(15, 15, 15, 0.16) 0px 16px 48px -8px` | Modals, dropdowns | + +### Decorative Depth + +- Hero workspace mockup card uses deep diffuse drop shadow (Level 3) — significant elevation against the navy band +- Pastel feature cards carry their own visual weight via tint backgrounds +- Sticky-note dot illustrations and mesh wires add atmospheric decoration to navy hero + +## Shapes + +### Border Radius Scale + +| Token | Value | Use | +| ---------------- | ------ | --------------------------------------------------- | +| `{rounded.xs}` | 4px | Tag chips | +| `{rounded.sm}` | 6px | Type badges | +| `{rounded.md}` | 8px | Buttons, inputs, search-pill | +| `{rounded.lg}` | 12px | Cards, pricing tiers, agent tiles, workspace mockup | +| `{rounded.xl}` | 16px | Larger feature panels | +| `{rounded.xxl}` | 20px | Featured product showcases | +| `{rounded.xxxl}` | 24px | Larger feature cards | +| `{rounded.full}` | 9999px | Status badges, pill tabs (NOT regular buttons) | + +Notion's geometry is sober-editorial — `{rounded.md}` (8px) buttons distinguish it from pill-button-everywhere brands. + +## Components + +> Per the no-hover policy, hover states are NOT documented. + +### Buttons + +**`button-primary`** — Signature purple rectangular primary CTA, the dominant action. + +- Background `{colors.primary}`, text `{colors.on-primary}`, typography `{typography.button-md}`, padding `10px 18px`, rounded `{rounded.md}`. +- Pressed state `button-primary-pressed` deepens to `{colors.primary-pressed}`. +- Disabled state uses `{colors.hairline}` background. + +**`button-dark`** — Black rectangular CTA on light backgrounds. + +- Background `{colors.ink-deep}`, text `{colors.on-dark}`, typography `{typography.button-md}`, padding `10px 18px`, rounded `{rounded.md}`. + +**`button-secondary`** — Outlined rectangular for secondary actions ("Request a demo"). + +- Background transparent, text `{colors.ink}`, border `1px solid {colors.hairline-strong}`, typography `{typography.button-md}`, padding `10px 18px`, rounded `{rounded.md}`. + +**`button-on-dark`** — White button on dark hero bands. + +- Background `{colors.on-dark}`, text `{colors.ink}`, typography `{typography.button-md}`, padding `10px 18px`, rounded `{rounded.md}`. + +**`button-secondary-on-dark`** — Outlined button on dark. + +- Background transparent, text `{colors.on-dark}`, border `1px solid {colors.on-dark-muted}`, typography `{typography.button-md}`, padding `10px 18px`, rounded `{rounded.md}`. + +**`button-ghost`** — Quieter ghost button. + +- Background transparent, text `{colors.ink}`, typography `{typography.button-md}`, padding `8px 12px`, rounded `{rounded.sm}`. + +**`button-link`** — Inline blue text link (NOT primary purple). + +- Background transparent, text `{colors.link-blue}`, typography `{typography.body-sm-medium}`, padding `0`. + +### Cards & Containers + +**`card-base`** — Standard content card. + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, padding `{spacing.xl}`, border `1px solid {colors.hairline}`. + +**`card-feature`** — Feature card with larger padding. + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, padding `{spacing.xxl}`, border `1px solid {colors.hairline}`. + +**`card-feature-yellow-bold`** — Bold yellow feature banner for high-emphasis content ("Ask your on-demand assistants"). + +- Background `{colors.card-tint-yellow-bold}`, text `{colors.charcoal}`, rounded `{rounded.lg}`, padding `{spacing.xxl}`. + +**`card-feature-peach`** + **`card-feature-rose`** + **`card-feature-mint`** + **`card-feature-sky`** + **`card-feature-lavender`** + **`card-feature-yellow`** + **`card-feature-cream`** — Pastel-tinted feature cards. + +- Each variant uses its corresponding `card-tint-*` color as background, text `{colors.charcoal}`, rounded `{rounded.lg}`, padding `{spacing.xxl}`. + +**`card-agent-tile`** — Agent assistant tile. + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, padding `{spacing.xl}`, border `1px solid {colors.hairline}`. + +**`card-template`** — Template thumbnail card. + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, padding `{spacing.lg}`, border `1px solid {colors.hairline}`. + +**`card-startup-perk`** — Startup-program perk grid item. + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, padding `{spacing.xl}`, border `1px solid {colors.hairline}`. + +**`pricing-card`** — Standard pricing tier card. + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, padding `{spacing.xxl}`, border `1px solid {colors.hairline}`. + +**`pricing-card-featured`** — Featured pricing tier (Plus or Business — purple-bordered). + +- Background `{colors.surface}`, rounded `{rounded.lg}`, padding `{spacing.xxl}`, border `2px solid {colors.primary}`. + +### Inputs & Forms + +**`text-input`** — Standard text field. + +- Background `{colors.canvas}`, text `{colors.ink}`, border `1px solid {colors.hairline-strong}`, rounded `{rounded.md}`, padding `{spacing.sm} {spacing.md}`, height 44px. + +**`text-input-focused`** — Activated state. + +- Border switches to `2px solid {colors.primary}` (purple). + +**`search-pill`** — Search bar. + +- Background `{colors.surface}`, text `{colors.steel}`, typography `{typography.body-md}`, rounded `{rounded.md}`, height 44px, border `1px solid {colors.hairline}`. + +### Tabs + +**`pill-tab`** + **`pill-tab-active`** — Pill-style tab nav for top-level switching. + +- Inactive: text `{colors.steel}`, border `1px solid {colors.hairline}`, padding `{spacing.xs} {spacing.md}`, rounded `{rounded.full}`. +- Active: background `{colors.ink-deep}`, text `{colors.on-dark}`. + +**`segmented-tab`** + **`segmented-tab-active`** — Underline-style tab navigation. + +- Inactive: text `{colors.steel}`, no border. Active: text `{colors.ink}`, 2px bottom border in `{colors.ink}`. + +### Badges & Status + +**`badge-purple`** — Purple status badge (matches primary CTA). + +- Background `{colors.primary}`, text `{colors.on-primary}`, typography `{typography.caption-bold}`, rounded `{rounded.full}`, padding `4px 10px`. + +**`badge-pink`** — Pink accent badge. + +- Background `{colors.brand-pink}`, text `{colors.on-primary}`, typography `{typography.caption-bold}`, rounded `{rounded.full}`, padding `4px 10px`. + +**`badge-orange`** — Orange accent badge. + +- Background `{colors.brand-orange}`, text `{colors.on-primary}`, typography `{typography.caption-bold}`, rounded `{rounded.full}`, padding `4px 10px`. + +**`badge-tag-purple`** — Soft-purple feature tag chip. + +- Background `{colors.card-tint-lavender}`, text `{colors.brand-purple-800}`, typography `{typography.caption-bold}`, rounded `{rounded.sm}`, padding `2px 8px`. + +**`badge-tag-orange`** — Soft-orange feature tag. + +- Background `{colors.card-tint-peach}`, text `{colors.brand-orange-deep}`, typography `{typography.caption-bold}`, rounded `{rounded.sm}`, padding `2px 8px`. + +**`badge-tag-green`** — Soft-mint feature tag. + +- Background `{colors.card-tint-mint}`, text `{colors.brand-green}`, typography `{typography.caption-bold}`, rounded `{rounded.sm}`, padding `2px 8px`. + +**`badge-popular`** — "Most Popular" tier indicator. + +- Background `{colors.primary}`, text `{colors.on-primary}`, typography `{typography.caption-bold}`, rounded `{rounded.full}`, padding `4px 10px`. + +**`promo-banner`** — Light surface promo strip ABOVE the top nav. + +- Background `{colors.surface}`, text `{colors.ink}`, typography `{typography.body-sm-medium}`, padding `{spacing.sm} {spacing.md}`. ("Developers: Get a first look at our new Developer Platform on May 13.") + +### Tables + +**`comparison-table`** — Pricing feature comparison table. + +- Background `{colors.canvas}`, text `{colors.ink}`, typography `{typography.body-sm}`, rounded `{rounded.md}`, border `1px solid {colors.hairline}`. + +**`comparison-row`** — Individual feature row. + +- Background `{colors.canvas}`, text `{colors.ink}`, padding `{spacing.md} {spacing.lg}`, bottom border `1px solid {colors.hairline-soft}`. + +### Documentation Components + +**`workspace-mockup-card`** — Embedded Notion workspace UI mockup on hero band ("Ramp HQ" kanban board). + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, border `1px solid {colors.hairline}`, deep shadow `rgba(15, 15, 15, 0.20) 0px 24px 48px -8px`. Carries actual Notion product UI mock. + +**`testimonial-card`** — Customer testimonial card. + +- Background `{colors.canvas}`, rounded `{rounded.lg}`, padding `{spacing.xxl}`, border `1px solid {colors.hairline}`. + +**`logo-wall-item`** — Customer logo wordmark cell. + +- Background transparent, text `{colors.steel}`, typography `{typography.body-md-medium}`, padding `{spacing.lg}`. + +**`faq-accordion-item`** — FAQ panel. + +- Background `{colors.canvas}`, rounded `{rounded.md}`, padding `{spacing.xl}`, bottom border `1px solid {colors.hairline}`. + +**`stat-row`** — Stats strip with bar chart visualization ("More productivity. Fewer tools."). + +- Background `{colors.surface}`, text `{colors.ink}`, rounded `{rounded.lg}`, padding `{spacing.section-sm}`. + +**`cta-banner-light`** — Light surface CTA banner. + +- Background `{colors.surface}`, text `{colors.ink}`, rounded `{rounded.lg}`, padding `{spacing.section}`. + +### Navigation + +**Top Navigation (Marketing)** — Sticky white bar. + +- Background `{colors.canvas}`, height ~64px, bottom border `1px solid {colors.hairline}`. +- Left: Notion "N" logo + "Product / AI / Solutions / Resources / Enterprise / Pricing / Request a demo" links. +- Right: "Get Notion free" purple button + "Log in" link. + +### Signature Components + +**`hero-band-dark`** — Deep navy hero band with embedded workspace mockup and decorative dots/wires. + +- Background `{colors.brand-navy}`, text `{colors.on-dark}`, padding `{spacing.hero}`. +- Layout: centered headline `{typography.hero-display}`, subtitle, button row (`button-primary` purple + `button-secondary-on-dark`), `workspace-mockup-card` below. +- Atmospheric decoration: scattered colorful sticky-note dots and mesh wire illustrations around the hero content (NOT a literal pattern fill — handled per-page via SVG/illustration). + +**`footer-region`** — Multi-column light footer. + +- Background `{colors.canvas}`, padding `{spacing.section} {spacing.xxl}`, top border `1px solid {colors.hairline}`. +- 6-column link grid (Product / Download / Resources / Notion for / Company / Legal). + +**`footer-link`** — Individual footer link. + +- Background transparent, text `{colors.steel}`, typography `{typography.body-sm}`, padding `{spacing.xxs} 0`. + +## Do's and Don'ts + +### Do + +- Use `{colors.primary}` (purple) as the dominant CTA across all surfaces — it's the brand's recognizable signal +- Pair deep navy hero bands ({colors.brand-navy}) with the purple button + decorative sticky-note dots +- Use pastel feature card tints (peach, rose, mint, lavender, sky, yellow) generously +- Use `{colors.card-tint-yellow-bold}` for high-emphasis "Ask the assistant"-style banner cards +- Apply `{rounded.md}` (8px) to buttons consistently — Notion uses rectangles, not pills +- Apply `{rounded.lg}` (12px) to all card families +- Maintain Notion-Sans across every UI surface +- Use the workspace mockup card on hero bands to show actual product UI + +### Don't + +- Don't use the purple for body text or large background surfaces +- Don't use pill-shaped buttons; Notion's geometry is rectangular-sober +- Don't mix link-blue ({colors.link-blue}) with primary-purple ({colors.primary}) — they have distinct roles +- Don't apply heavy shadows on flat documentation cards +- Don't replace Notion-Sans with a generic Inter + +## Responsive Behavior + +### Breakpoints + +| Name | Width | Key Changes | +| -------------- | ------------- | --------------------------------------- | +| Mobile (small) | < 480px | Single column. Hero 36px. Pricing 1-up. | +| Mobile (large) | 480 – 767px | Feature cards 2-up. Hero 48px. | +| Tablet | 768 – 1023px | 2-column feature grids. Hero 56px. | +| Desktop | 1024 – 1279px | 4-tier pricing card row. Hero 72px. | +| Wide Desktop | ≥ 1280px | Full 80px hero presentation. | + +### Touch Targets + +- Buttons render at 40–44px effective height +- Form inputs render at 44px height +- Pill tabs ~32px → 44px on mobile + +### Collapsing Strategy + +- **Promo banner** stays full-width; truncates at < 480px +- **Top nav** below 1024px collapses to hamburger +- **Hero band**: workspace mockup card moves below text/buttons on mobile +- **Pricing tiers**: 4-column → 2-column tablet → 1-column mobile +- **Feature cards**: 3-up desktop → 2-up tablet → 1-up mobile +- **Hero typography**: 80px → 56px → 48px → 36px +- **Footer**: 6-column desktop → 3-column tablet → accordion mobile + +### Image Behavior + +- Workspace mockup card maintains aspect ratio +- Pastel illustrations inside feature cards scale proportionally +- Customer logo wall: wordmarks at consistent 60–80px height + +## Iteration Guide + +1. Focus on ONE component at a time +2. Reference component names and tokens directly +3. Run `npx @google/design.md lint DESIGN.md` after edits +4. Add new variants as separate `components:` entries +5. Default to `{typography.body-md}` for body +6. Keep `{colors.primary}` (purple) as the primary CTA — distinct from `{colors.link-blue}` for inline links +7. Use `{rounded.md}` for buttons (rectangles), `{rounded.lg}` for cards, `{rounded.full}` for pill tabs/badges only + +## Known Gaps + +- Specific dark-mode token values not surfaced beyond hero bands +- Animation/transition timings not extracted; recommend 150–200ms ease +- Form validation success state not explicitly captured +- Pastel-tint mapping (which feature uses which tint) is observation-based — the actual brand library may have more entries diff --git a/README.md b/README.md index ba59743..c5ea831 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ Nuxt 3 basic starter setup. +## Design System + +`DESIGN.md` is the source of truth for the repository's visual system. When the implementation and the design spec differ, update the code to follow [`DESIGN.md`](./DESIGN.md). + +```bash +npm run design:lint +``` + ## Setup ```bash diff --git a/assets/scss/components/about/_about-page.scss b/assets/scss/components/about/_about-page.scss index e7929bf..4c747f5 100644 --- a/assets/scss/components/about/_about-page.scss +++ b/assets/scss/components/about/_about-page.scss @@ -3,12 +3,16 @@ .about-page { display: grid; - gap: 28px; + gap: 32px; &__hero { display: grid; gap: 12px; - padding: 0 0 8px; + padding: 32px; + border: 1px solid $border-soft; + border-radius: $rounded-xl; + background: rgba(255, 255, 255, 0.9); + box-shadow: $shadow-card; } &__contact { @@ -61,7 +65,11 @@ &__section { display: grid; gap: 24px; - padding-top: 6px; + padding: 28px; + border: 1px solid $border-soft; + border-radius: $rounded-lg; + background: rgba(255, 255, 255, 0.86); + box-shadow: $shadow-card; } &__section-head { @@ -87,14 +95,6 @@ grid-template-columns: repeat(3, minmax(0, 1fr)); } - &__chip { - margin: 0; - padding: 14px 0; - color: $text-subtle; - font-size: 0.98rem; - font-weight: 700; - } - &__experience-list { display: grid; gap: 18px; @@ -144,6 +144,11 @@ } @media (max-width: 1024px) { + &__hero, + &__section { + padding: 24px 20px; + } + &__chips { grid-template-columns: 1fr; } diff --git a/assets/scss/components/base/_badge.scss b/assets/scss/components/base/_badge.scss new file mode 100644 index 0000000..cf0ae92 --- /dev/null +++ b/assets/scss/components/base/_badge.scss @@ -0,0 +1,47 @@ +@use '../../settings/tokens' as *; + +.base-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 24px; + padding: 2px 8px; + border-radius: $rounded-sm; + background: $color-card-tint-lavender; + color: $color-brand-purple-800; + font-size: 0.8125rem; + font-weight: 600; + line-height: 1.4; + white-space: nowrap; + + &--purple { + padding: 4px 10px; + border-radius: $rounded-full; + background: $color-primary; + color: $color-on-primary; + } + + &--pink { + padding: 4px 10px; + border-radius: $rounded-full; + background: $color-card-tint-rose; + color: $color-brand-pink-deep; + } + + &--orange { + padding: 4px 10px; + border-radius: $rounded-full; + background: $color-card-tint-peach; + color: $color-brand-orange-deep; + } + + &--green { + background: $color-card-tint-mint; + color: $color-charcoal; + } + + &--lavender { + background: $color-card-tint-lavender; + color: $color-brand-purple-800; + } +} diff --git a/assets/scss/components/base/_button.scss b/assets/scss/components/base/_button.scss index d41a2d8..0a53ab7 100644 --- a/assets/scss/components/base/_button.scss +++ b/assets/scss/components/base/_button.scss @@ -5,55 +5,126 @@ display: inline-flex; align-items: center; justify-content: center; - gap: 8px; - min-height: 38px; - padding: 0 14px; - border: 1px solid $border-strong; - border-radius: $rounded-lg; - background: transparent; - color: $foreground; + gap: $space-xs; + min-height: 40px; + padding: 10px 18px; + border: 1px solid transparent; + border-radius: $rounded-md; + background: $color-primary; + color: $color-on-primary; font: inherit; - font-size: 0.95rem; - font-weight: 700; - line-height: 1; + font-size: 0.875rem; + font-weight: 500; + line-height: 1.3; cursor: pointer; text-decoration: none; transition: color 0.18s ease, - background 0.18s ease, - border-color 0.18s ease; - - &:hover:not(:disabled) { - background: rgba(53, 197, 240, 0.06); - border-color: $brand; - color: $brand-strong; - } + background-color 0.18s ease, + border-color 0.18s ease, + box-shadow 0.18s ease; &:focus-visible { @include focus-ring(); } - &:disabled { - opacity: 0.65; - cursor: wait; - box-shadow: none; + &:hover:not(:disabled):not([aria-disabled='true']) { + background: $color-primary-pressed; } + &:disabled, &[aria-disabled='true'] { - opacity: 0.65; + border-color: $color-hairline; + background: $color-hairline; + color: $color-muted; cursor: not-allowed; box-shadow: none; + text-decoration: none; + } + + &[aria-disabled='true'] { + pointer-events: none; + } + + &--primary { + background: $color-primary; + color: $color-on-primary; + + &.base-button--on-dark { + background: $color-on-dark; + color: $color-ink; + + &:hover:not(:disabled):not([aria-disabled='true']) { + background: $color-hairline-soft; + } + } } &--secondary { background: transparent; - color: $brand-strong; - border-color: $border-strong; - box-shadow: none; + color: $color-ink; + border-color: $color-hairline-strong; + + &:hover:not(:disabled):not([aria-disabled='true']) { + background: $color-surface-soft; + border-color: $color-ink; + color: $color-ink; + } + + &.base-button--on-dark { + color: $color-on-dark; + border-color: $color-on-dark-muted; + + &:hover:not(:disabled):not([aria-disabled='true']) { + background: rgba(255, 255, 255, 0.08); + border-color: $color-on-dark; + color: $color-on-dark; + } + } + } + + &--ghost { + padding: 8px 12px; + background: transparent; + color: $color-ink; + + &:hover:not(:disabled):not([aria-disabled='true']) { + background: $color-hairline-soft; + color: $color-ink; + } + + &.base-button--on-dark { + color: $color-on-dark; + + &:hover:not(:disabled):not([aria-disabled='true']) { + background: rgba(255, 255, 255, 0.08); + color: $color-on-dark; + } + } + } + + &--link { + min-height: auto; + padding: 0; + border-color: transparent; + background: transparent; + color: $color-link-blue; + font-size: 0.875rem; + text-decoration: none; + + &:hover:not(:disabled):not([aria-disabled='true']) { + background: transparent; + color: $color-link-blue-pressed; + text-decoration: underline; + text-underline-offset: 0.14em; + } + + &.base-button--on-dark { + color: $color-on-dark; - &:hover:not(:disabled) { - background: rgba(53, 197, 240, 0.04); - box-shadow: none; + &:hover:not(:disabled):not([aria-disabled='true']) { + color: $color-hairline-soft; + } } } } diff --git a/assets/scss/components/base/_card.scss b/assets/scss/components/base/_card.scss index 8f52030..0174d31 100644 --- a/assets/scss/components/base/_card.scss +++ b/assets/scss/components/base/_card.scss @@ -1,33 +1,101 @@ @use '../../settings/tokens' as *; .base-card { - padding: 0; - border-radius: 0; - background: transparent; - border: 0; - box-shadow: none; display: grid; - gap: 10px; + gap: $space-lg; + color: $color-charcoal; - &:hover { - background: transparent; + &--elevated { + padding: $space-xl; + border: 1px solid $color-hairline; + border-radius: $rounded-lg; + background: $color-canvas; + box-shadow: $shadow-card; } - &--elevated { + &--base { + padding: 0; + border: 0; + border-radius: 0; background: transparent; } + &--feature, + &--testimonial, + &--pricing { + padding: $space-xxl; + } + + &--template { + padding: $space-lg; + } + + &--feature { + gap: $space-lg; + } + + &--template { + gap: $space-sm; + } + + &--pricing { + gap: $space-xl; + } + + &--tone-peach { + background: $color-card-tint-peach; + color: $color-charcoal; + border-color: transparent; + } + + &--tone-rose { + background: $color-card-tint-rose; + color: $color-charcoal; + border-color: transparent; + } + + &--tone-mint { + background: $color-card-tint-mint; + color: $color-charcoal; + border-color: transparent; + } + + &--tone-lavender { + background: $color-card-tint-lavender; + color: $color-charcoal; + border-color: transparent; + } + + &--tone-sky { + background: $color-card-tint-sky; + color: $color-charcoal; + border-color: transparent; + } + + &--tone-yellow { + background: $color-card-tint-yellow; + color: $color-charcoal; + border-color: transparent; + } + + &--tone-cream { + background: $color-card-tint-cream; + color: $color-charcoal; + border-color: transparent; + } + &__header, + &__body, &__footer { display: grid; - gap: 8px; + gap: $space-md; } &__title { margin: 0; - color: $foreground; - font-size: 1rem; + color: inherit; + font-size: 1.08rem; line-height: 1.4; - font-weight: 700; + font-weight: 600; } } diff --git a/assets/scss/components/home/_home-page.scss b/assets/scss/components/home/_home-page.scss index 1a1dc9b..28f6995 100644 --- a/assets/scss/components/home/_home-page.scss +++ b/assets/scss/components/home/_home-page.scss @@ -3,31 +3,52 @@ .home-page { display: grid; - gap: 30px; + gap: 32px; &__hero { display: grid; - gap: 10px; - padding: 0 0 6px; + gap: 14px; + padding: 40px; + border-radius: $rounded-xl; + background: + radial-gradient(circle at top right, rgba(255, 100, 200, 0.18), transparent 24%), + radial-gradient(circle at bottom left, rgba(86, 69, 212, 0.22), transparent 28%), + $brand-navy; + box-shadow: $shadow-float; } &__eyebrow, &__section-eyebrow { - @include eyebrow(); + @include eyebrow(rgba(255, 255, 255, 0.72)); } &__title { @include page-title(clamp(2.4rem, 4.2vw, 3.7rem), 1); + max-width: 12ch; + color: #ffffff; } &__lead, &__summary { @include lead(66ch, 1.02rem); + color: rgba(255, 255, 255, 0.8); + } + + &__actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + padding-top: 4px; } &__section { display: grid; gap: 18px; + padding: 28px; + border: 1px solid $border-soft; + border-radius: $rounded-lg; + background: rgba(255, 255, 255, 0.86); + box-shadow: $shadow-card; } &__section-head { @@ -51,13 +72,14 @@ &__note-item { display: grid; - gap: 4px; + gap: 6px; + padding: 16px 18px; + border-radius: $rounded-lg; + background: $brand-lavender; } &__note-link { @include item-link; - text-decoration: underline; - text-underline-offset: 3px; } &__note-lead { @@ -66,5 +88,10 @@ @media (max-width: 1024px) { gap: 24px; + + &__hero, + &__section { + padding: 24px 20px; + } } } diff --git a/assets/scss/components/layout/_app-footer.scss b/assets/scss/components/layout/_app-footer.scss index 416cbb0..5131624 100644 --- a/assets/scss/components/layout/_app-footer.scss +++ b/assets/scss/components/layout/_app-footer.scss @@ -1,10 +1,48 @@ -@use '../../settings/tokens' as *; +@use "../../settings/tokens" as *; +@use "../../settings/mixins" as *; .app-footer { - padding: 12px 0 0; + border-top: 1px solid $border-soft; + background: $surface-elevated; color: $text-muted; + &__inner { + width: min(100%, 1080px); + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + gap: $space-md; + padding: $space-xl $space-section-sm $space-section-sm; + } + &__text { margin: 0; + color: $text-muted; + font-size: 0.88rem; + line-height: 1.6; + } + + &__link { + color: $text-muted; + font-size: 0.88rem; + line-height: 1.5; + + &:hover { + color: $brand-strong; + } + + &:focus-visible { + @include focus-ring(); + color: $brand-strong; + } + } + + @media (max-width: 1024px) { + &__inner { + align-items: flex-start; + flex-direction: column; + padding: $space-lg $space-lg $space-xxxl; + } } } diff --git a/assets/scss/components/layout/_app-header.scss b/assets/scss/components/layout/_app-header.scss index 6ce3f6f..558adda 100644 --- a/assets/scss/components/layout/_app-header.scss +++ b/assets/scss/components/layout/_app-header.scss @@ -1,31 +1,34 @@ @use "../../settings/tokens" as *; @use "../../settings/mixins" as *; +$app-header-hover-border: rgba($color-primary, 0.12); +$app-header-active-border: rgba($color-primary, 0.18); + .app-header { position: sticky; top: 0; z-index: 20; display: grid; grid-template-rows: auto auto 1fr; - gap: 22px; + gap: $space-xl; align-content: start; - padding: 28px 22px 24px 24px; - background: #ffffff; + padding: $space-xl $space-lg $space-xl $space-xl; + background: $surface-soft; border-right: 1px solid $border-soft; height: 100dvh; overflow: auto; &__brand { display: grid; - gap: 6px; + gap: $space-xs; &-mark { display: inline-flex; align-items: center; - gap: 10px; + gap: $space-sm; font-size: 0.98rem; - font-weight: 800; - color: $foreground; + font-weight: 700; + color: $text-primary; &:focus-visible { @include focus-ring(); @@ -35,38 +38,44 @@ &-subtitle { font-size: 0.9rem; - color: $text-muted; + color: $text-subtle; + line-height: 1.6; } } &__nav { display: grid; - gap: 2px; - padding-top: 0; + gap: $space-xs; + padding-top: $space-sm; + border-top: 1px solid $border-soft; } &__link { @include nav-entry(0, 42px, 2px); - padding-left: 14px; + padding-left: $space-sm; + padding-right: $space-sm; display: flex; align-items: center; justify-content: space-between; &:hover { - background: rgba(53, 197, 240, 0.04); + background: $surface-elevated; color: $brand-strong; + border-color: $app-header-hover-border; } &.router-link-active, &.router-link-exact-active { color: $brand-strong; border-left-color: $brand; + border-color: $app-header-active-border; + background: $brand-lavender; } } &__accordion { display: grid; - gap: 4px; + gap: $space-xxs; } &__summary { @@ -82,7 +91,7 @@ content: ""; width: 16px; height: 16px; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23006780' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='lucide lucide-chevron-down-icon lucide-chevron-down'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234534b3' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-size: contain; background-repeat: no-repeat; background-position: center; @@ -96,17 +105,18 @@ &__subnav { display: grid; - gap: 0; + gap: $space-xxs; + padding: $space-xxs 0 $space-xs; } &__subnav-section { display: grid; - gap: 4px; + gap: $space-xxs; } &__subnav-title { - margin: 0 0 2px; - padding-left: 14px; + margin: 0 0 $space-xxs; + padding-left: $space-sm; font-size: 0.76rem; font-weight: 800; letter-spacing: 0.08em; @@ -119,34 +129,38 @@ font-size: 0.92rem; &:hover { - background: rgba(53, 197, 240, 0.04); + background: $surface-elevated; color: $brand-strong; + border-color: $app-header-hover-border; } &.router-link-active, &.router-link-exact-active { color: $brand-strong; border-left-color: $brand; + border-color: $app-header-active-border; + background: $brand-lavender; } } &__subnav-divider { height: 1px; - margin: 10px 0 8px; + margin: $space-sm 0 $space-xs; background: $border-soft; } &__sidebar-note { display: grid; - gap: 8px; + gap: $space-xs; align-self: end; - padding-top: 16px; + padding-top: $space-lg; + border-top: 1px solid $border-soft; &-title { display: inline-flex; width: fit-content; font-size: 0.82rem; - font-weight: 800; + font-weight: 700; color: $brand-strong; letter-spacing: 0.08em; @@ -161,6 +175,10 @@ max-height: none; min-height: 0; top: auto; + height: auto; border-right: 0; + overflow: visible; + grid-template-rows: auto; + padding-bottom: $space-lg; } } diff --git a/assets/scss/components/notes/_notes-content-page.scss b/assets/scss/components/notes/_notes-content-page.scss index f302c66..5e4273a 100644 --- a/assets/scss/components/notes/_notes-content-page.scss +++ b/assets/scss/components/notes/_notes-content-page.scss @@ -27,6 +27,8 @@ &__article { @include prose-stack; + gap: 32px; + max-width: 74ch; color: $foreground; > :first-child { @@ -75,9 +77,10 @@ code { padding: 0.15em 0.4em; - border-radius: 4px; - background: rgba(53, 197, 240, 0.12); - color: $brand-strong; + border-radius: $rounded-sm; + background: rgba(86, 69, 212, 0.1); + color: $text-primary; + font-weight: 600; font-size: 0.95em; } @@ -85,27 +88,32 @@ overflow-x: auto; margin: 0; padding: 18px; - border-radius: 16px; - background: #f5fbfd; - color: $foreground; - line-height: 1.75; + border-radius: $rounded-lg; + border: 1px solid rgba(86, 69, 212, 0.16); + background: linear-gradient(180deg, #f7f4ff 0%, #f3f0ff 100%); + color: $text-primary; + line-height: 1.7; white-space: pre; + + code { + padding: 0; + border-radius: 0; + background: transparent; + color: inherit; + font-weight: 500; + } } blockquote { margin: 0; padding: 14px 18px; - border-left: 3px solid rgba(53, 197, 240, 0.35); + border-left: 3px solid rgba(86, 69, 212, 0.35); color: $text-subtle; - background: rgba(53, 197, 240, 0.04); + background: rgba(86, 69, 212, 0.04); } .base-card { - margin-top: 4px; - } - - .base-card + .base-card { - margin-top: 0; + margin-top: 24px; } } } diff --git a/assets/scss/components/notes/_notes-index-page.scss b/assets/scss/components/notes/_notes-index-page.scss index 94237df..ea8d13f 100644 --- a/assets/scss/components/notes/_notes-index-page.scss +++ b/assets/scss/components/notes/_notes-index-page.scss @@ -41,9 +41,10 @@ padding: 20px; border: 1px solid $border-soft; border-radius: $rounded-lg; - background: #fff; + background: $surface-elevated; color: inherit; text-decoration: none; + box-shadow: $shadow-card; transition: transform 0.18s ease, border-color 0.18s ease, @@ -52,13 +53,29 @@ &:hover { transform: translateY(-2px); border-color: $brand; - box-shadow: 0 12px 30px rgba(16, 24, 40, 0.08); + box-shadow: $shadow-float; } &:focus-visible { @include focus-ring(); border-color: $brand; - box-shadow: 0 12px 30px rgba(16, 24, 40, 0.08); + box-shadow: $shadow-float; + } + + &:nth-child(4n + 1) { + background: $brand-lavender; + } + + &:nth-child(4n + 2) { + background: $brand-peach; + } + + &:nth-child(4n + 3) { + background: $brand-mint; + } + + &:nth-child(4n + 4) { + background: $surface-soft; } } diff --git a/assets/scss/components/posts/_posts-archive-page.scss b/assets/scss/components/posts/_posts-archive-page.scss index 862aafb..cc3aaec 100644 --- a/assets/scss/components/posts/_posts-archive-page.scss +++ b/assets/scss/components/posts/_posts-archive-page.scss @@ -23,12 +23,17 @@ &__divider { height: 1px; margin: 4px 0 0; - background: transparent; + background: $border-soft; } &__section { display: grid; - gap: 14px; + gap: 18px; + padding: 24px; + border: 1px solid $border-soft; + border-radius: $rounded-lg; + background: rgba(255, 255, 255, 0.86); + box-shadow: $shadow-card; } &__section-title { @@ -41,10 +46,10 @@ &__list { display: grid; - gap: 18px; + gap: 14px; margin: 0; - padding-left: 22px; - list-style: disc; + padding: 0; + list-style: none; } &__item { @@ -56,8 +61,5 @@ @include item-link; font-size: 1.15rem; line-height: 1.45; - text-decoration: underline; - text-underline-offset: 3px; } } - diff --git a/assets/scss/components/posts/_posts-link-item.scss b/assets/scss/components/posts/_posts-link-item.scss index e1b731b..178e42c 100644 --- a/assets/scss/components/posts/_posts-link-item.scss +++ b/assets/scss/components/posts/_posts-link-item.scss @@ -3,7 +3,11 @@ .post-link-item { @include stack(8px); - padding: 0; + padding: 16px 18px; + border: 1px solid $border-soft; + border-radius: $rounded-lg; + background: $surface-elevated; + box-shadow: $shadow-card; &__head { display: flex; @@ -15,8 +19,6 @@ @include item-link; font-size: 1.08rem; line-height: 1.5; - text-decoration: underline; - text-underline-offset: 3px; } &__meta { diff --git a/assets/scss/components/posts/_posts-page.scss b/assets/scss/components/posts/_posts-page.scss index 1ef2879..13ab420 100644 --- a/assets/scss/components/posts/_posts-page.scss +++ b/assets/scss/components/posts/_posts-page.scss @@ -28,10 +28,15 @@ &__group { display: grid; - gap: 14px; + gap: 16px; + padding: 24px; + border: 1px solid $border-soft; + border-radius: $rounded-lg; + background: rgba(255, 255, 255, 0.86); + box-shadow: $shadow-card; &:not(:first-of-type) { - margin-top: 26px; + margin-top: 18px; } } @@ -45,7 +50,7 @@ &__group-outline { padding-left: 16px; - border-left: 1px solid $border-soft; + border-left: 2px solid $border-soft; } &__items { @@ -65,7 +70,7 @@ color: $text-muted; font-size: clamp(1rem, 1.25vw, 1.2rem); line-height: 1.6; - font-weight: 500; + font-weight: 600; text-decoration: none; white-space: normal; diff --git a/assets/scss/components/posts/_posts-queue-page.scss b/assets/scss/components/posts/_posts-queue-page.scss index d829233..68e9755 100644 --- a/assets/scss/components/posts/_posts-queue-page.scss +++ b/assets/scss/components/posts/_posts-queue-page.scss @@ -23,7 +23,7 @@ &__divider { height: 1px; margin: 4px 0 0; - background: transparent; + background: $border-soft; } &__categories { @@ -33,6 +33,11 @@ &__category { display: grid; gap: 16px; + padding: 24px; + border: 1px solid $border-soft; + border-radius: $rounded-lg; + background: rgba(255, 255, 255, 0.86); + box-shadow: $shadow-card; } &__category-title { diff --git a/assets/scss/layouts/_default.scss b/assets/scss/layouts/_default.scss index ced9b41..f0cb79b 100644 --- a/assets/scss/layouts/_default.scss +++ b/assets/scss/layouts/_default.scss @@ -5,21 +5,23 @@ position: relative; min-height: 100svh; display: grid; - grid-template-columns: 292px minmax(0, 1fr); + grid-template-columns: 304px minmax(0, 1fr); align-items: stretch; + background: $surface-soft; .skip-link { position: absolute; - left: 18px; - top: 12px; + left: $space-lg; + top: $space-sm; z-index: 40; - padding: 10px 14px; + padding: $space-sm $space-md; border: 1px solid $brand; + border-radius: $rounded-md; background: $surface-elevated; color: $brand-strong; font-size: 0.92rem; font-weight: 700; - transform: translateY(-180%); + transform: translateY(calc(-100% - #{$space-sm})); transition: transform 0.18s ease; &:focus-visible { @@ -31,15 +33,17 @@ .app-content { min-width: 0; display: grid; + grid-template-rows: minmax(0, 1fr) auto; gap: 0; align-content: start; + background: linear-gradient(180deg, $surface-soft 0%, $surface 100%); } .app-main { - width: 100%; - margin: 0; + width: min(100%, 1080px); + margin: 0 auto; min-width: 0; - padding: 48px 40px 56px; + padding: $space-section-sm $space-section-sm $space-section; } @media (max-width: 1024px) { @@ -47,8 +51,8 @@ padding: 0; .skip-link { - left: 12px; - top: 10px; + left: $space-sm; + top: $space-sm; } .app-content { @@ -56,7 +60,8 @@ } .app-main { - padding: 24px 18px 40px; + width: 100%; + padding: $space-xl $space-lg $space-section-sm; border-left: 0; } } diff --git a/assets/scss/layouts/_error.scss b/assets/scss/layouts/_error.scss index ac80a98..e5fb098 100644 --- a/assets/scss/layouts/_error.scss +++ b/assets/scss/layouts/_error.scss @@ -6,8 +6,8 @@ place-items: center; padding: 24px; background: - radial-gradient(circle at top, rgba(53, 197, 240, 0.16), transparent 36%), - linear-gradient(180deg, #f6faff 0%, #edf3f8 100%); + radial-gradient(circle at top, rgba(86, 69, 212, 0.18), transparent 34%), + linear-gradient(180deg, #fafaf9 0%, #f6f5f4 100%); } .error-card { diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 349447a..ed53eb3 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -2,6 +2,7 @@ @use './layouts/default'; @use './layouts/error'; @use './components/base/button'; +@use './components/base/badge'; @use './components/base/icon'; @use './components/base/card'; @use './components/about/about-page'; @@ -19,10 +20,10 @@ :root { color-scheme: light; font-family: - 'Pretendard', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + 'Inter', 'Pretendard', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - background: #ffffff; - color: #171c20; + background: #f6f5f4; + color: #1a1a1a; } html, @@ -32,5 +33,7 @@ body, } body { - background: #ffffff; + background: + radial-gradient(circle at top left, rgba(86, 69, 212, 0.06), transparent 28%), + linear-gradient(180deg, #fafaf9 0%, #f6f5f4 100%); } diff --git a/assets/scss/settings/_mixins.scss b/assets/scss/settings/_mixins.scss index 671affd..39afeec 100644 --- a/assets/scss/settings/_mixins.scss +++ b/assets/scss/settings/_mixins.scss @@ -5,21 +5,36 @@ gap: $gap; } -@mixin eyebrow($color: $brand-strong, $size: 0.78rem) { +@mixin eyebrow($color: $text-muted, $size: 0.76rem) { margin: 0; color: $color; font-size: $size; - font-weight: 800; + font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } @mixin page-title($size: clamp(2.4rem, 4vw, 3.6rem), $line-height: 1.08) { margin: 0; - color: $foreground; + color: $text-primary; font-size: $size; line-height: $line-height; - font-weight: 800; + font-weight: 600; + letter-spacing: 0; +} + +@mixin page-header($gap: $space-sm, $max-width: 72ch) { + display: grid; + gap: $gap; + max-width: $max-width; +} + +@mixin section-panel($padding: $space-xxl, $radius: $rounded-lg) { + border: 1px solid $border-soft; + border-radius: $radius; + background: $surface-elevated; + box-shadow: $shadow-card; + padding: $padding; } @mixin lead($max-width: 68ch, $size: 1rem) { @@ -27,26 +42,26 @@ max-width: $max-width; color: $text-subtle; font-size: $size; - line-height: 1.8; + line-height: 1.7; } @mixin meta { margin: 0; color: $text-muted; - font-size: 0.92rem; + font-size: 0.88rem; } @mixin focus-ring($offset: 2px) { - outline: 0; - outline: 2px solid $focus-ring; + outline: 2px solid transparent; outline-offset: $offset; + box-shadow: 0 0 0 3px $focus-ring; } @mixin item-link { - color: $foreground; + color: $text-primary; font-size: 1.05rem; - line-height: 1.6; - font-weight: 700; + line-height: 1.55; + font-weight: 600; &:hover { color: $brand-strong; @@ -81,15 +96,25 @@ line-height: 1.85; } +@mixin prose { + @include prose-stack; + + p, + li { + @include prose-text; + } +} + @mixin nav-entry($padding-left: 18px, $height: 42px, $border-width: 2px) { position: relative; min-height: $height; padding: 9px 10px 9px $padding-left; - border: 0; + border: 1px solid transparent; + border-radius: $rounded-md; border-left: $border-width solid transparent; background: transparent; color: $text-subtle; - font-weight: 700; + font-weight: 600; transition: color 0.18s ease, border-color 0.18s ease, @@ -97,7 +122,7 @@ &:focus-visible { @include focus-ring(); - background: rgba(53, 197, 240, 0.06); + background: rgba(86, 69, 212, 0.08); color: $brand-strong; } } @@ -112,7 +137,7 @@ height: $size; border-radius: 9999px; transform: translateY(-50%); - background: rgba(53, 197, 240, 0.2); + background: rgba(86, 69, 212, 0.16); transition: background 0.18s ease; } diff --git a/assets/scss/settings/_tokens.scss b/assets/scss/settings/_tokens.scss index 2470524..552dfc6 100644 --- a/assets/scss/settings/_tokens.scss +++ b/assets/scss/settings/_tokens.scss @@ -1,26 +1,105 @@ -$surface: #f6faff; -$surface-elevated: #ffffff; -$surface-card: #ffffff; -$surface-soft: #f0f4f9; -$surface-muted: #eaeef3; -$foreground: #171c20; -$text-primary: #2f3438; -$text-muted: #828c94; -$text-subtle: #5f6b72; -$rounded-sm: 0px; -$rounded-md: 2px; -$rounded-lg: 4px; -$rounded-xl: 6px; +$color-primary: #5645d4; +$color-primary-pressed: #4534b3; +$color-primary-deep: #3a2a99; +$color-on-primary: #ffffff; +$color-brand-navy: #0a1530; +$color-brand-navy-deep: #070f24; +$color-brand-navy-mid: #1a2a52; +$color-link-blue: #0075de; +$color-link-blue-pressed: #005bab; +$color-brand-orange: #dd5b00; +$color-brand-orange-deep: #793400; +$color-brand-pink: #ff64c8; +$color-brand-pink-deep: #a02e6d; +$color-brand-purple: #7b3ff2; +$color-brand-purple-300: #d6b6f6; +$color-brand-purple-800: #391c57; +$color-brand-teal: #2a9d99; +$color-brand-green: #1aae39; +$color-brand-yellow: #f5d75e; +$color-brand-brown: #523410; +$color-card-tint-peach: #ffe8d4; +$color-card-tint-rose: #fde0ec; +$color-card-tint-mint: #d9f3e1; +$color-card-tint-lavender: #e6e0f5; +$color-card-tint-sky: #dcecfa; +$color-card-tint-yellow: #fef7d6; +$color-card-tint-yellow-bold: #f9e79f; +$color-card-tint-cream: #f8f5e8; +$color-card-tint-gray: #f0eeec; +$color-canvas: #ffffff; +$color-surface: #f6f5f4; +$color-surface-soft: #fafaf9; +$color-hairline: #e5e3df; +$color-hairline-soft: #ede9e4; +$color-hairline-strong: #c8c4be; +$color-ink-deep: #000000; +$color-ink: #1a1a1a; +$color-charcoal: #37352f; +$color-slate: #5d5b54; +$color-steel: #787671; +$color-stone: #a4a097; +$color-muted: #bbb8b1; +$color-on-dark: #ffffff; +$color-on-dark-muted: #a4a097; +$color-semantic-success: #1aae39; +$color-semantic-warning: #dd5b00; +$color-semantic-error: #e03131; +$color-success: $color-semantic-success; +$color-warning: $color-semantic-warning; +$color-error: $color-semantic-error; + +$rounded-xs: 4px; +$rounded-sm: 6px; +$rounded-md: 8px; +$rounded-lg: 12px; +$rounded-xl: 16px; +$rounded-xxl: 20px; +$rounded-xxxl: 24px; $rounded-full: 9999px; -$border-soft: #eaecef; -$border-strong: #dfe4ea; -$shadow-card: none; -$shadow-float: none; -$shadow-inset: rgba(22, 28, 32, 0.02); -$brand: #35c5f0; -$brand-strong: #006780; -$brand-contrast: #ffffff; -$brand-soft: #f6faff; -$danger-bg: rgba(186, 26, 26, 0.08); -$danger-text: #ba1a1a; -$focus-ring: rgba(53, 197, 240, 0.28); + +$space-xxs: 4px; +$space-xs: 8px; +$space-sm: 12px; +$space-md: 16px; +$space-lg: 20px; +$space-xl: 24px; +$space-xxl: 32px; +$space-xxxl: 40px; +$space-section-sm: 48px; +$space-section: 64px; +$space-section-lg: 96px; +$space-hero: 120px; + +$shadow-card: 0 12px 24px -16px rgba(15, 15, 15, 0.18); +$shadow-float: 0 24px 48px -24px rgba(15, 15, 15, 0.28); +$shadow-hero: 0 32px 80px -36px rgba(7, 15, 36, 0.42); +$focus-ring: rgba(86, 69, 212, 0.24); + +$brand: $color-primary; +$brand-strong: $color-primary-pressed; +$brand-contrast: $color-on-primary; +$brand-soft: $color-card-tint-lavender; +$brand-navy: $color-brand-navy; +$brand-orange: $color-brand-orange; +$brand-orange-deep: $color-brand-orange-deep; +$brand-pink: $color-brand-pink; +$brand-pink-deep: $color-brand-pink-deep; +$brand-green: $color-brand-green; +$brand-lavender: $color-card-tint-lavender; +$brand-mint: $color-card-tint-mint; +$brand-peach: $color-card-tint-peach; + +$surface: $color-surface; +$surface-elevated: $color-canvas; +$surface-card: $color-canvas; +$surface-soft: $color-surface-soft; +$surface-muted: $color-hairline-soft; +$foreground: $color-ink; +$text-primary: $color-charcoal; +$text-muted: $color-steel; +$text-subtle: $color-slate; +$border-soft: $color-hairline; +$border-strong: $color-hairline-strong; +$danger-bg: rgba(224, 49, 49, 0.08); +$danger-text: $color-error; diff --git a/components/about/AboutPage.vue b/components/about/AboutPage.vue index c7d38f9..5ba8803 100644 --- a/components/about/AboutPage.vue +++ b/components/about/AboutPage.vue @@ -38,8 +38,10 @@ @@ -72,6 +74,7 @@ diff --git a/components/base/BaseButton.vue b/components/base/BaseButton.vue index d619adc..3d6cc0a 100644 --- a/components/base/BaseButton.vue +++ b/components/base/BaseButton.vue @@ -11,22 +11,33 @@ diff --git a/components/base/BaseCard.vue b/components/base/BaseCard.vue index c8ada77..7c49526 100644 --- a/components/base/BaseCard.vue +++ b/components/base/BaseCard.vue @@ -17,18 +17,35 @@ diff --git a/components/home/HomePage.vue b/components/home/HomePage.vue index 4db25bb..b7a1f0b 100644 --- a/components/home/HomePage.vue +++ b/components/home/HomePage.vue @@ -7,6 +7,11 @@

읽을 내용은 링크로, 정리는 메모로, 기록은 작업일지로 나눠서 관리합니다.

+ +
+ 읽을 내용 보기 + 작업일지 보기 +
@@ -45,6 +50,7 @@ +``` + +- [ ] **Step 6: Run tests and commit** + +Run: `npm run test` + +Expected: existing tests pass. + +```bash +git add components/base assets/scss/components/base assets/scss/main.scss +git commit -m "refactor: 공통 컴포넌트 디자인 시스템 반영" +``` + +## Task 3: App Shell, Header, Footer + +**Files:** +- Modify: `layouts/default.vue` +- Modify: `assets/scss/layouts/_default.scss` +- Modify: `components/layout/AppHeader.vue` +- Modify: `assets/scss/components/layout/_app-header.scss` +- Modify: `components/layout/AppFooter.vue` +- Modify: `assets/scss/components/layout/_app-footer.scss` + +- [ ] **Step 1: Restore footer in default layout** + +Change `layouts/default.vue` so the footer renders: + +```vue +
+
+ +
+ + +
+``` + +- [ ] **Step 2: Align shell surface and spacing** + +Update `_default.scss` so content uses `canvas` and responsive spacing: + +```scss +.app-shell { + min-height: 100svh; + display: grid; + grid-template-columns: 304px minmax(0, 1fr); + background: $color-surface; + + .app-content { + min-width: 0; + display: grid; + align-content: start; + background: $color-canvas; + } + + .app-main { + width: min(100%, 1080px); + margin: 0 auto; + padding: $space-section-sm $space-xxl $space-section; + } +} +``` + +- [ ] **Step 3: Keep header behavior, refresh header styling** + +Do not remove the route watcher or `details/summary` logic in `AppHeader.vue`. Update only markup if classes need clearer BEM names. In `_app-header.scss`, use: + +```scss +.app-header { + position: sticky; + top: 0; + min-height: 100svh; + padding: $space-xl; + border-right: 1px solid $color-hairline; + background: $color-surface-soft; + color: $color-charcoal; +} +``` + +- [ ] **Step 4: Refresh nav entry states** + +Use `@include nav-entry()` and active styles: + +```scss +.app-header__link, +.app-header__summary, +.app-header__sublink { + @include nav-entry(); + text-decoration: none; + + &[aria-current='page'], + &.router-link-active { + border-left-color: $color-primary; + background: $color-card-tint-lavender; + color: $color-primary-deep; + } +} +``` + +- [ ] **Step 5: Update footer to `footer-region`** + +Keep `AppFooter.vue` simple: + +```vue + +``` + +- [ ] **Step 6: Run tests and commit** + +Run: `npm run test` + +Expected: existing tests pass. + +```bash +git add layouts/default.vue assets/scss/layouts/_default.scss components/layout assets/scss/components/layout +git commit -m "refactor: 앱 셸과 내비게이션 디자인 시스템 반영" +``` + +## Task 4: Home And Page Groups + +**Files:** +- Modify: `components/home/HomePage.vue` +- Modify: `assets/scss/components/home/_home-page.scss` +- Modify: `components/posts/PostIndexPage.vue` +- Modify: `components/posts/PostQueuePage.vue` +- Modify: `components/posts/PostArchivePage.vue` +- Modify: `components/posts/PostLinkItem.vue` +- Modify: `assets/scss/components/posts/_posts-page.scss` +- Modify: `assets/scss/components/posts/_posts-queue-page.scss` +- Modify: `assets/scss/components/posts/_posts-archive-page.scss` +- Modify: `assets/scss/components/posts/_posts-link-item.scss` + +- [ ] **Step 1: Apply `hero-band-dark` to home without changing data flow** + +Keep `readingQueuePreview` and `noteLinks`. Update button variants: + +```vue +읽을 내용 보기 +작업일지 보기 +``` + +- [ ] **Step 2: Style home hero and sections from `DESIGN.md`** + +Use deep navy and pastel tones: + +```scss +.home-page__hero { + display: grid; + gap: $space-md; + padding: clamp($space-xxl, 7vw, $space-hero); + background: $color-brand-navy; + color: $color-on-dark; +} + +.home-page__section { + @include section-panel($space-xl); +} +``` + +- [ ] **Step 3: Normalize posts page headers and dividers** + +Keep current `PostIndexPage.vue` data resolution. Use shared page header SCSS: + +```scss +.posts-page__header, +.posts-queue-page__header, +.posts-archive-page__header { + @include page-header; +} + +.posts-page__divider, +.posts-queue-page__divider, +.posts-archive-page__divider { + height: 1px; + background: $color-hairline; +} +``` + +- [ ] **Step 4: Keep outline list behavior for worklog** + +Preserve `작업내용` and `다른작업` groups. Style outline links: + +```scss +.posts-page__group-outline { + border-left: 1px solid $color-hairline; + padding-left: $space-lg; +} + +.posts-page__outline-link { + display: inline-flex; + flex-wrap: wrap; + gap: $space-xs; + color: $color-charcoal; + text-decoration: none; +} +``` + +- [ ] **Step 5: Keep external link accessibility** + +Do not remove `aria-label`, `rel`, or `target` from `PostLinkItem.vue` and `PostArchivePage.vue`. + +- [ ] **Step 6: Run tests and commit** + +Run: `npm run test` + +Expected: existing tests pass. + +```bash +git add components/home components/posts assets/scss/components/home assets/scss/components/posts +git commit -m "refactor: 홈과 작업일지 화면 디자인 시스템 반영" +``` + +## Task 5: Notes, About, Error + +**Files:** +- Modify: `components/notes/NotesIndexPage.vue` +- Modify: `components/notes/NoteContentPage.vue` +- Modify: `assets/scss/components/notes/_notes-index-page.scss` +- Modify: `assets/scss/components/notes/_notes-content-page.scss` +- Modify: `components/about/AboutPage.vue` +- Modify: `assets/scss/components/about/_about-page.scss` +- Modify: `components/error/ErrorPage.vue` +- Modify: `assets/scss/components/error/_error-page.scss` +- Modify: `assets/scss/layouts/_error.scss` + +- [ ] **Step 1: Keep note data flow and apply card tones** + +Preserve `queryCollection('notes')` and category counts. Style cards: + +```scss +.notes-index-page__card { + display: grid; + gap: $space-sm; + padding: $space-xl; + border: 1px solid $color-hairline; + border-radius: $rounded-lg; + background: $color-card-tint-cream; + color: $color-charcoal; + text-decoration: none; +} +``` + +- [ ] **Step 2: Apply prose rhythm to note content** + +Use existing `ContentRenderer` and update article style: + +```scss +.note-content-page__article { + @include prose-stack; + max-width: 74ch; + + :where(p, li) { + @include prose-text; + } + + :where(a) { + color: $color-link-blue; + } +} +``` + +- [ ] **Step 3: Refresh about page without changing content** + +Keep existing `skills` and `experiences`. Style chips as tag badges: + +```scss +.about-page__chip { + display: inline-flex; + align-items: center; + min-height: 28px; + padding: 2px $space-sm; + border-radius: $rounded-sm; + background: $color-card-tint-lavender; + color: $color-brand-purple-800; + font-size: 0.8125rem; + font-weight: 600; +} +``` + +- [ ] **Step 4: Align error page with base card/button variants** + +Keep `role="alert"` and `aria-live`. Update retry button: + +```vue + + 다시 시도 + +``` + +- [ ] **Step 5: Run tests and commit** + +Run: `npm run test` + +Expected: existing tests pass. + +```bash +git add components/notes components/about components/error assets/scss/components/notes assets/scss/components/about assets/scss/components/error assets/scss/layouts/_error.scss +git commit -m "refactor: 메모 소개 오류 화면 디자인 시스템 반영" +``` + +## Task 6: Verification And Cleanup + +**Files:** +- Inspect: all modified files from Tasks 1-5 +- Optional Modify: `assets/scss/main.scss` +- Optional Modify: `README.md` + +- [ ] **Step 1: Search for obsolete style names** + +Run: `rg -n "base-button--secondary-on-dark|base-button--secondary|shadow-inset|letter-spacing: -" assets components layouts pages` + +Expected: no stale button modifier dependency that conflicts with new variant classes, and no negative letter spacing. + +- [ ] **Step 2: Verify new component usage** + +Run: `rg -n "BaseBadge|BaseSectionHeader" components pages` + +Expected: `BaseBadge` has at least one usage. `BaseSectionHeader` should not appear because this plan keeps section headings in page components. + +- [ ] **Step 3: Run Vitest** + +Run: `npm run test` + +Expected: all existing tests pass. + +- [ ] **Step 4: Try design lint without treating network failure as implementation failure** + +Run: `npm run design:lint` + +Expected: pass if `@google/design.md` is available. If it fails with `ENOTFOUND registry.npmjs.org`, record that network prevented verification. + +- [ ] **Step 5: Do not run build unless explicitly requested** + +Do not run `npm run build` because `AGENTS.md` says builds are only run when the user asks. + +- [ ] **Step 6: Commit cleanup** + +```bash +git add assets components layouts pages README.md +git commit -m "chore: 디자인 시스템 적용 후 정리" +``` + +If there are no cleanup changes, skip this commit. + +## Self-Review + +- Spec coverage: Tasks 1-6 cover tokens, mixins, base components, app shell, layout navigation, footer, all page groups, accessibility, verification, and cleanup. +- Red-flag scan: No unresolved implementation markers are used. +- Type consistency: `ButtonVariant`, `CardVariant`, and `CardTone` names are introduced before later tasks reference them. +- Scope check: The plan does not rewrite routes, Markdown content, data sources, or deployment configuration. diff --git a/docs/superpowers/specs/2026-05-06-design-system-refresh-design.md b/docs/superpowers/specs/2026-05-06-design-system-refresh-design.md new file mode 100644 index 0000000..436209d --- /dev/null +++ b/docs/superpowers/specs/2026-05-06-design-system-refresh-design.md @@ -0,0 +1,98 @@ +# DESIGN.md 기반 전체 UI 재정리 설계 + +## 목적 + +`DESIGN.md`를 저장소의 시각 기준으로 삼아 기존 Nuxt 블로그 UI 전체를 다시 정리한다. 라우트, 콘텐츠 구조, 문서형 IA는 유지하고, 색상, 타이포그래피, 간격, 버튼, 카드, 헤더, 푸터, 페이지별 SCSS를 디자인 시스템 기준으로 일관되게 맞춘다. + +## 범위 + +적용 범위는 전체 UI다. + +- 공통 토큰: `assets/scss/settings/_tokens.scss`, `assets/scss/settings/_mixins.scss` +- 공통 컴포넌트: `components/base/*`, `assets/scss/components/base/*` +- 레이아웃: `layouts/default.vue`, `assets/scss/layouts/_default.scss` +- 내비게이션과 푸터: `components/layout/*`, `assets/scss/components/layout/*` +- 페이지 컴포넌트와 SCSS: 홈, 읽을 내용, 보관함, 작업일지, 메모, 소개, 에러 화면 + +범위 밖 작업은 콘텐츠 재작성, 라우트 변경, Markdown 데이터 구조 변경, 배포 설정 변경이다. 필요한 경우 깨진 참조 정리만 한다. + +## 설계 방향 + +기존 구조를 유지하면서 디자인 시스템을 치환한다. `DESIGN.md`의 Notion 계열 톤을 그대로 마케팅 페이지처럼 복제하지 않고, 이 블로그의 글 중심 구조에 맞춰 절제해서 적용한다. + +핵심 원칙은 다음과 같다. + +- 사이드바와 본문 중심의 문서형 레이아웃 유지 +- `DESIGN.md`의 deep navy, primary purple, warm neutral surface, pastel tint를 제한적으로 사용 +- 버튼은 8px rounded rectangle 기준으로 통일 +- 카드는 반복 항목과 명확한 콘텐츠 묶음에만 사용 +- 불필요한 shadow, border, radius를 줄이고 hairline divider 중심으로 정리 +- 모든 페이지에서 heading, list, link, focus state 접근성 유지 + +## 토큰 구조 + +`_tokens.scss`는 `DESIGN.md`의 top-level 토큰을 SCSS 변수로 옮긴다. + +- 색상: `primary`, `brand-navy`, `link-blue`, `card-tint-*`, `surface`, `hairline`, `ink`, `charcoal`, `slate`, `steel`, semantic color +- radius: `xs`, `sm`, `md`, `lg`, `xl`, `full` +- spacing: `xxs`부터 section spacing까지 +- typography: SCSS mixin에서 사용할 font size, weight, line height 기준 + +기존 SCSS가 쓰는 `$brand`, `$surface`, `$text-*`, `$border-*` 같은 변수는 alias로 유지한다. 이렇게 하면 기존 파일을 한 번에 깨뜨리지 않고 점진적으로 `DESIGN.md` 명칭으로 수렴할 수 있다. + +## 공통 컴포넌트 + +`BaseButton`은 variant 기반으로 확장한다. + +- `primary`: `button-primary` +- `secondary`: `button-secondary` +- `onDark`: dark surface 위 primary/secondary 표현 +- `ghost`: 낮은 강조도의 내비게이션 또는 보조 액션 +- `link`: 텍스트 링크형 액션 + +기존 `secondary`, `onDark`, `disabled`, `to`, `type` 사용 방식은 유지한다. 새 API가 필요하면 기존 prop과 충돌하지 않게 `variant`를 추가한다. + +`BaseCard`는 `variant`와 `tone`을 분리한다. + +- `variant`: `base`, `feature`, `template`, `testimonial`, `pricing` +- `tone`: `canvas`, `peach`, `rose`, `mint`, `lavender`, `sky`, `yellow`, `cream` + +반복적으로 필요한 작은 라벨은 `BaseBadge`를 추가할 수 있다. 섹션 제목 패턴이 여러 페이지에 중복되면 `BaseSectionHeader`를 추가할 수 있다. 새 컴포넌트는 실제 중복을 줄일 때만 만든다. + +## 페이지 적용 + +홈은 `hero-band-dark`를 사용한다. 큰 마케팅 히어로가 아니라 블로그 진입점으로 유지하고, 읽을 내용과 메모 진입을 명확히 둔다. 작업일지와 읽을 내용 목록은 outline/list 중심을 유지한다. + +헤더는 현재 `details/summary` 기반 접근성 구조를 유지한다. active state, hover, focus, subnav divider를 `DESIGN.md`의 warm neutral, hairline, primary purple 기준으로 통일한다. + +푸터는 `footer-region`, `footer-link` 기준으로 정리한다. 링크 모음 이상의 역할을 늘리지 않는다. + +메모, 작업일지, 읽을 내용, 보관함, 소개, 에러 페이지는 같은 section heading, list rhythm, card tone, link style을 공유한다. 페이지별 고유한 정보 구조는 유지한다. + +## 접근성 + +- 현재 skip link 유지 +- 내비게이션 `aria-current` 유지 +- `details/summary`의 키보드 동작 유지 +- 링크와 버튼 focus-visible 상태를 모든 variant에서 확인 +- 색상 대비가 낮은 pastel tint 위에는 `charcoal` 또는 `ink` 계열 텍스트 사용 +- 장식 요소는 CSS background 또는 `aria-hidden`으로 처리 + +## 테스트와 검증 + +저장소 규칙상 사용자가 명시하지 않았으므로 `npm run build`는 실행하지 않는다. + +구현 후 가능한 검증은 다음 순서로 수행한다. + +- TypeScript 또는 Vue 오류를 잡기 위한 정적 확인 +- 기존 Vitest 테스트 +- `npm run design:lint`는 네트워크 또는 패키지 사용 가능 여부에 따라 시도 +- 주요 페이지의 구조와 접근성 상태를 수동 점검 + +## 완료 기준 + +- 전체 페이지가 `DESIGN.md`의 색상, 간격, radius, component tone을 공유한다. +- 기존 라우트와 콘텐츠 렌더링이 유지된다. +- 누락된 공통 컴포넌트가 있으면 추가되어 페이지별 중복이 줄어든다. +- 접근성 속성과 키보드 사용성이 기존보다 나빠지지 않는다. +- 사용하지 않는 새 파일이나 폴더가 남지 않는다. diff --git a/layouts/default.vue b/layouts/default.vue index 1c35d4c..4fc9e01 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -8,7 +8,7 @@ - + diff --git a/package.json b/package.json index de75e9b..bb22518 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "nuxt dev", "build": "nuxt build", + "design:lint": "npx @google/design.md lint DESIGN.md", "generate": "nuxt generate", "test": "vitest run", "preview": "nuxt preview", diff --git a/pages/posts/index.vue b/pages/posts/index.vue index 3b713d3..60e5f15 100644 --- a/pages/posts/index.vue +++ b/pages/posts/index.vue @@ -8,6 +8,6 @@ import { useBlogSeo } from '~/composables/useBlogSeo' useBlogSeo({ title: '작업일지', - description: '프로젝트를 하면서 남긴 기록을 작업내용과 다른작업으로 나누어 모아둔 페이지입니다.', + description: 'Figma에서 DESIGN.md를 추출하고 연동 흐름까지 정리한 작업일지 페이지입니다.', })