diff --git a/.claude/skills/frontend-design/SKILL.md b/.claude/skills/frontend-design/SKILL.md new file mode 100644 index 00000000..600b6db4 --- /dev/null +++ b/.claude/skills/frontend-design/SKILL.md @@ -0,0 +1,42 @@ +--- +name: frontend-design +description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics. +license: Complete terms in LICENSE.txt +--- + +This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. + +The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. + +## Design Thinking + +Before coding, understand the context and commit to a BOLD aesthetic direction: +- **Purpose**: What problem does this interface solve? Who uses it? +- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. +- **Constraints**: Technical requirements (framework, performance, accessibility). +- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? + +**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. + +Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: +- Production-grade and functional +- Visually striking and memorable +- Cohesive with a clear aesthetic point-of-view +- Meticulously refined in every detail + +## Frontend Aesthetics Guidelines + +Focus on: +- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. +- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. +- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. +- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. +- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. + +NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. + +Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. + +**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. + +Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..3e792b29 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,109 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Angular 20 frontend for [techinterview.space](https://techinterview.space) - a platform for interview management, salary data analysis, and company reviews. Uses Server-Side Rendering (SSR) for SEO and social sharing. + +## Build & Test Commands + +```bash +npm install # Install dependencies +npm start # Dev server at http://localhost:4200 +npm run build # Production build with SSR +npm run serve:ssr:interviewer # Build + run SSR server at http://localhost:4000 +npm test # Run tests (uses ChromiumNoSandbox via Puppeteer) +npm run lint # ESLint +npm run pretty-all # Prettier on all files +npm run bundle:analyze # Webpack bundle analyzer +``` + +Docker: +```bash +docker build -t techinterview-frontend . +docker run -p 8080:4000 techinterview-frontend +``` + +## Architecture + +### Directory Structure +``` +src/app/ +├── components/ # Global components (navbar, admin-navbar, maintenance) +├── models/ # TypeScript interfaces and enums +├── modules/ # Feature modules (lazy-loaded) +│ ├── admin/ # Admin panel +│ ├── companies/ # Company reviews +│ ├── home/ # Landing, auth callbacks, about pages +│ ├── interviews/ # Interview templates and sessions +│ ├── salaries/ # Salary charts and surveys +│ └── users/ # User profiles +├── services/ # API clients (exported via index.ts barrel) +└── shared/ # Reusable components, directives, guards, interceptors +``` + +### Path Aliases (tsconfig.json) +- `@app/*` → `app/*` +- `@components/*` → `src/app/components/*` +- `@environments/*` → `src/environments/*` +- `@shared/*` → `src/app/shared/*` +- `@models/*` → `src/app/models/*` +- `@modules/*` → `src/app/modules/*` +- `@services/*` → `src/app/services/*` + +### SSR Configuration +Hybrid SSR/SPA approach configured in `src/app/app.routes.server.ts`: +- **SSR (RenderMode.Server)**: Home, companies, about pages - for SEO/meta tags +- **SPA (RenderMode.Client)**: Admin, salaries, interviews, auth callbacks - no SSR needed + +Key SSR files: +- `src/server.ts` - Express.js SSR server +- `src/main.server.ts` - Server-side Angular bootstrap +- `src/app/app.module.server.ts` - Server-specific module with Auth0 mock +- `src/app/services/meta-tags.service.ts` - Dynamic meta tag management + +### HTTP Interceptors (order matters) +1. `AuthInterceptor` - Adds Auth0 bearer token +2. `SpinnerInterceptor` - Shows loading spinner +3. `DateParserInterceptor` - Parses date strings from API + +### Guards +- `AuthGuard` - Requires authenticated user +- `AdminGuard` - Requires admin role +- `ActiveUserGuard` - Requires active (non-banned) user + +## Code Style + +Follow `.eslintrc.js`: +- Component selectors: `app-kebab-case` +- Directive selectors: `appCamelCase` +- Unused variables: prefix with `_` + +## Testing + +Jasmine/Karma with ChromiumNoSandbox (Puppeteer). Test files colocated with components (`*.spec.ts`). + +Test utilities in `src/app/shared/test-utils/`. + +## Key Patterns + +### Services +All application services are registered in `src/app/services/index.ts` and provided in `AppModule`. + +### Shared Module +Reusable components, directives, and pipes are declared/exported in `src/app/shared/shared.module.ts`. + +### Environment Configuration +- `src/environments/environment.ts` - Development +- `src/environments/environment.prod.ts` - Production +- `src/environments/environment.staging.ts` - Staging + +## Dependencies + +- Auth: Auth0 (`@auth0/auth0-angular`) +- UI: Bootstrap 5, Bootstrap Icons +- Charts: Chart.js, `@sgratzl/chartjs-chart-boxplot` +- Markdown: `ngx-markdown` with Prism.js syntax highlighting +- Analytics: `ngx-google-analytics` +- Forms: `@ng-select/ng-select` for dropdowns diff --git a/REDESIGN.md b/REDESIGN.md new file mode 100644 index 00000000..98b88ce2 --- /dev/null +++ b/REDESIGN.md @@ -0,0 +1,765 @@ +# Techinterview.space Redesign Plan + +## "Warm & Friendly" Theme + +A comprehensive redesign focused on readability, comfort for long reading sessions, and a welcoming aesthetic. + +--- + +## 1. Design Philosophy + +### Goals +- **Eye comfort**: Reduce strain for users reading reviews and analyzing charts +- **Clear hierarchy**: Easy scanning of data and content +- **Subtle depth**: Soft shadows to create visual layers +- **Warmth**: Inviting, approachable feel that builds trust +- **Consistency**: Unified design language across all pages + +### Target Users +Users who spend extended time on the site reading company reviews, analyzing salary charts, and comparing data. + +--- + +## 2. Design System + +### 2.1 Color Palette + +#### Backgrounds (Warm Paper Tones) +| Variable | Hex | Usage | +|----------|-----|-------| +| `$warm-bg-primary` | `#fffbf7` | Main page background | +| `$warm-bg-secondary` | `#fef7f0` | Alternating sections | +| `$warm-bg-tertiary` | `#fff9f2` | Card backgrounds | +| `$warm-bg-accent` | `#fdf4e8` | Highlighted areas | + +#### Text Colors (Reduced Eye Strain) +| Variable | Hex | Usage | +|----------|-----|-------| +| `$warm-text-primary` | `#3d3d3d` | Body text | +| `$warm-text-secondary` | `#5c5c5c` | Secondary text | +| `$warm-text-muted` | `#7a7a7a` | Captions, hints | +| `$warm-text-heading` | `#2d2d2d` | Headings | + +#### Accent Colors +| Variable | Hex | Usage | +|----------|-----|-------| +| `$warm-accent-primary` | `#e07a5f` | Primary buttons, links (Terracotta) | +| `$warm-accent-secondary` | `#81b29a` | Success states, secondary actions (Sage) | +| `$warm-accent-tertiary` | `#f2cc8f` | Highlights, warnings (Gold) | +| `$warm-accent-soft` | `#f4a261` | CTAs, attention elements (Orange) | + +#### Semantic Colors (Adjusted for Warmth) +| Variable | Hex | Usage | +|----------|-----|-------| +| `$warm-danger` | `#d95550` | Errors, destructive actions | +| `$warm-warning` | `#e9a820` | Warnings | +| `$warm-success` | `#81b29a` | Success (same as secondary) | +| `$warm-info` | `#6fa8c7` | Information | + +### 2.2 Typography + +#### Font Stack +```scss +// Headings - Elegant serif for warmth +$font-heading: 'Lora', Georgia, serif; + +// Body - Rounded sans-serif for readability +$font-body: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + +// Monospace - For code/data +$font-mono: 'JetBrains Mono', 'Fira Code', monospace; +``` + +#### Type Scale +| Element | Font | Size | Weight | Line Height | +|---------|------|------|--------|-------------| +| h1 | Lora | 2.5rem | 700 | 1.15 | +| h2 | Lora | 2rem | 600 | 1.2 | +| h3 | Lora | 1.5rem | 600 | 1.25 | +| h4 | Lora | 1.25rem | 600 | 1.3 | +| Body | Nunito | 1rem | 400 | 1.6 | +| Body Large | Nunito | 1.1rem | 400 | 1.7 | +| Small | Nunito | 0.875rem | 400 | 1.5 | +| Caption | Nunito | 0.75rem | 400 | 1.4 | + +### 2.3 Shadow System + +Layered, warm-tinted shadows for natural depth: + +```scss +$warm-shadow-color: rgba(61, 45, 35, 0.08); +$warm-shadow-color-medium: rgba(61, 45, 35, 0.12); +$warm-shadow-color-strong: rgba(61, 45, 35, 0.16); + +$shadow-sm: + 0 1px 2px $warm-shadow-color, + 0 2px 4px $warm-shadow-color; + +$shadow-md: + 0 2px 4px $warm-shadow-color, + 0 4px 8px $warm-shadow-color, + 0 8px 16px $warm-shadow-color; + +$shadow-lg: + 0 4px 8px $warm-shadow-color, + 0 8px 16px $warm-shadow-color, + 0 16px 32px $warm-shadow-color-medium; + +$shadow-xl: + 0 8px 16px $warm-shadow-color, + 0 16px 32px $warm-shadow-color-medium, + 0 32px 64px $warm-shadow-color-strong; +``` + +### 2.4 Spacing & Layout + +```scss +$border-radius-sm: 6px; +$border-radius-md: 10px; +$border-radius-lg: 16px; +$border-radius-xl: 24px; + +$spacing-unit: 0.5rem; // 8px base +``` + +### 2.5 Transitions + +```scss +$transition-fast: 0.15s ease; +$transition-normal: 0.2s ease; +$transition-slow: 0.3s ease; +``` + +--- + +## 3. Component Updates + +### 3.1 Global Components + +#### Navbar +- [ ] Background: `$warm-bg-primary` with subtle bottom border +- [ ] Subtle shadow on scroll +- [ ] Brand: Lora font, warm dark color +- [ ] Links: Nunito, hover with terracotta accent +- [ ] Dropdowns: Rounded corners, soft shadow + +#### Footer +- [ ] Background: `#3d3d3d` (warm dark gray, not pure black) +- [ ] Text: Light warm gray `#f5f5f5` +- [ ] Links: Muted on default, terracotta on hover +- [ ] Icons: Sage green accents + +#### Buttons +- [ ] Primary: Terracotta fill, white text, rounded corners +- [ ] Secondary: Sage green fill +- [ ] Outline: Border with transparent fill, fill on hover +- [ ] All: Soft shadow, lift on hover, press effect on active + +#### Cards +- [ ] Background: White with warm tint +- [ ] Border-radius: 12px +- [ ] Shadow: `$shadow-md` +- [ ] Hover: Lift effect with `$shadow-lg` + +#### Forms +- [ ] Inputs: Warm border, rounded corners +- [ ] Focus: Terracotta border glow +- [ ] Labels: Secondary text color +- [ ] Error states: Warm red + +#### Alerts +- [ ] Rounded corners +- [ ] Soft colored backgrounds (not harsh) +- [ ] Matching border color + +--- + +## 4. Module-by-Module Plan + +### Phase 1: Foundation (Priority: Critical) + +#### 1.1 Global Styles +Files to modify: +- [ ] `src/_colors.scss` → Create new `src/_warm-theme.scss` +- [ ] `src/styles.scss` → Update with warm theme variables +- [ ] `src/index.html` → Add Google Fonts, CSS variables + +#### 1.2 Core Layout +- [ ] `src/app/components/navbar/navbar.component.scss` +- [ ] `src/app/components/admin-navbar/admin-navbar.component.scss` +- [ ] `src/index.html` → Footer styles + +--- + +### Phase 2: Shared Components (Priority: High) + +Files to update: +- [ ] `shared/components/submit-button/submit-button.component.scss` +- [ ] `shared/components/loading-button/loading-button.component.scss` +- [ ] `shared/components/go-back-button/go-back-button.component.scss` +- [ ] `shared/components/app-page-header/app-page-header.component.scss` +- [ ] `shared/components/dialogs/dialog/dialog.component.scss` +- [ ] `shared/components/dialogs/confirm-dialog/confirm-dialog.component.scss` +- [ ] `shared/components/dialogs/decline-dialog/decline-dialog.component.scss` +- [ ] `shared/components/alert/component/alert.component.scss` +- [ ] `shared/components/loading-spinner/loading-spinner.component.scss` +- [ ] `shared/components/app-spinner/app-spinner.component.scss` +- [ ] `shared/components/field-error/field-error.component.scss` +- [ ] `shared/components/label/label.component.scss` +- [ ] `shared/components/star-rating/star-rating.component.scss` +- [ ] `shared/components/star-rating-readonly/star-rating-readonly.component.scss` +- [ ] `shared/components/vote-buttons/vote-buttons.component.scss` +- [ ] `shared/components/share-button/share-button.component.scss` +- [ ] `shared/components/developer-grade-label/developer-grade-label.component.scss` +- [ ] `shared/components/add-salary-progress-bar/form-progress-bar.component.scss` +- [ ] `shared/components/labels-ng-select/labels-ng-select.component.scss` +- [ ] `shared/components/items-per-day-chart/items-per-day-chart.component.scss` +- [ ] `shared/components/wednesday-frog/wednesday-frog.component.scss` + +--- + +### Phase 3: Home Module (Priority: High) + +Files to update: +- [ ] `modules/home/components/home/home.component.scss` +- [ ] `modules/home/components/about-us/about-us.component.scss` +- [ ] `modules/home/components/telegram-bot/telegram-bot.component.scss` +- [ ] `modules/home/components/me/me.component.scss` +- [ ] `modules/home/components/unsubscribe-me/unsubscribe-me.component.scss` +- [ ] `modules/home/components/error-page-component/error-page-component.component.scss` + +--- + +### Phase 4: Salaries Module (Priority: High) + +This is a core feature with many charts. Files to update: +- [ ] `modules/salaries/components/salaries-chart/salaries-chart.component.scss` +- [ ] `modules/salaries/components/add-salary/add-salary.component.scss` +- [ ] `modules/salaries/components/edit-salary/edit-salary.component.scss` +- [ ] `modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.scss` +- [ ] `modules/salaries/components/salaries-chart/currency-select-box/currency-select-box.component.scss` +- [ ] `modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.scss` +- [ ] `modules/salaries/components/salaries-chart/salary-block-remote-value/salary-block-remote-value.component.scss` +- [ ] `modules/salaries/components/salaries-chart/salaries-by-grade/salaries-by-grade-block.component.scss` +- [ ] `modules/salaries/components/salaries-chart/salaries-adding-chart/salaries-adding-chart.component.scss` +- [ ] `modules/salaries/components/salaries-chart/predefined-filter-info/predefined-info-block.component.scss` +- [ ] `modules/salaries/components/salaries-chart/usefulness-rating/usefulness-rating.component.scss` +- [ ] `modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss` +- [ ] `modules/salaries/components/grades-min-max-salaries-chart/grades-min-max-chart.component.scss` +- [ ] `modules/salaries/components/people-by-grades-chart/people-by-grades-chart.component.scss` +- [ ] `modules/salaries/components/people-by-gender-chart/people-by-gender-chart.component.scss` +- [ ] `modules/salaries/components/people-by-gender-chart/salaries-by-gender-chart/salaries-by-gender-chart.component.scss` +- [ ] `modules/salaries/components/people-by-age-chart/people-by-age-chart.component.scss` +- [ ] `modules/salaries/components/salaries-by-age-chart/salaries-by-age-chart.component.scss` +- [ ] `modules/salaries/components/people-by-experience-chart/people-by-experience-chart.component.scss` +- [ ] `modules/salaries/components/salaries-by-experience-chart/salaries-by-experience-chart.component.scss` +- [ ] `modules/salaries/components/salaries-by-cities-chart/salaries-by-city-chart.component.scss` +- [ ] `modules/salaries/components/cities-doughnut-chart/cities-doughnut-chart.component.scss` +- [ ] `modules/salaries/components/work-industries-chart/work-industries-chart.component.scss` +- [ ] `modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss` +- [ ] `modules/salaries/components/salaries-skills-chart/salaries-skills-chart.component.scss` +- [ ] `modules/salaries/components/historical-charts-page/historical-charts-page.component.scss` +- [ ] `modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-salaries-chart.component.scss` +- [ ] `modules/salaries/components/historical-charts-page/survey-historical-chart/survey-historical-chart.component.scss` +- [ ] `modules/salaries/components/currencies-chart-page/currencies-chart-page.component.scss` + +--- + +### Phase 5: Companies Module (Priority: High) + +Files to update: +- [ ] `modules/companies/components/companies-page/companies-page.component.scss` +- [ ] `modules/companies/components/company-page/company-page.component.scss` +- [ ] `modules/companies/components/add-review-page/add-review-page.component.scss` +- [ ] `modules/companies/components/recent-reviews/recent-reviews-page.component.scss` +- [ ] `modules/companies/components/company-review-block/company-review-block.component.scss` + +--- + +### Phase 6: Interviews Module (Priority: Medium) + +Files to update: +- [ ] `modules/interviews/components/interviews-home/interviews-home.component.scss` +- [ ] `modules/interviews/components/interview-page/interview-page.component.scss` +- [ ] `modules/interviews/components/interview-edit-page/interview-edit-page.component.scss` +- [ ] `modules/interviews/components/interview-template-page/interview-template-page.component.scss` +- [ ] `modules/interviews/components/interview-template-edit-page/interview-template-edit-page.component.scss` +- [ ] `modules/interviews/components/my-interviews/my-interviews.component.scss` +- [ ] `modules/interviews/components/my-interview-templates/my-interview-templates.component.scss` +- [ ] `modules/interviews/components/public-interview-templates/public-interview-templates.component.scss` +- [ ] `modules/interviews/components/text-with-linebreaks/text-with-linebreaks.component.scss` + +--- + +### Phase 7: Users Module (Priority: Medium) + +Files to update: +- [ ] `modules/users/components/user-page/user-page.component.scss` + +--- + +### Phase 8: Admin Module (Priority: Low) + +Admin pages are less critical but should be consistent: +- [ ] `modules/admin/components/admin-start-page/admin-start-page.component.scss` +- [ ] `modules/admin/components/jobs-table/jobs-table.component.scss` +- [ ] `modules/admin/components/background-jobs/background-jobs.component.scss` +- [ ] `modules/admin/components/health-check-table/health-check-table.component.scss` +- [ ] `modules/admin/components/companies/companies-admin-page/companies-admin-page.component.scss` +- [ ] `modules/admin/components/companies/company-admin-page/company-admin-page.component.scss` +- [ ] `modules/admin/components/companies/reviews-to-approve/reviews-to-approve-page.component.scss` +- [ ] `modules/admin/components/generate-qr-code-page/generate-qr-page.component.scss` +- [ ] `modules/admin/components/currencies-page/currencies-page.component.scss` +- [ ] `modules/admin/components/openai-prompts/openai-prompts-admin-page.component.scss` +- [ ] `modules/admin/components/salaries/historical-data-templates/historical-data-templates.component.scss` +- [ ] `modules/admin/components/telegram/reviews-stat-subscriptions/reviews-stat-subscriptions.component.scss` +- [ ] `modules/admin/components/telegram/salaries-stat-subscriptions/stat-data-cache-records.component.scss` +- [ ] `modules/admin/components/telegram/job-posting-message-subscriptions/job-posting-message-subscriptions.component.scss` +- [ ] `modules/admin/components/tools/send-email-page/send-email-page.component.scss` + +--- + +### Phase 9: App-Level Components (Priority: Medium) + +- [ ] `src/app/app.component.scss` +- [ ] `src/app/components/maintenance/maintenance.component.scss` + +--- + +## 5. Chart Theming + +Charts need special attention for the warm theme: + +### Chart.js / ng2-charts Configuration +```typescript +// Suggested warm chart colors +const chartColors = { + primary: '#e07a5f', // Terracotta + secondary: '#81b29a', // Sage + tertiary: '#f2cc8f', // Gold + quaternary: '#f4a261', // Orange + quinary: '#6fa8c7', // Soft blue + senary: '#c4a1d4', // Soft purple +}; + +// Grid and axis styling +const gridColor = 'rgba(61, 45, 35, 0.08)'; +const textColor = '#5c5c5c'; +``` + +--- + +## 6. Implementation Strategy + +### Approach: Bottom-Up +1. Create theme file with all variables +2. Update global styles +3. Update shared components (used everywhere) +4. Update page-specific components module by module + +### File Structure +``` +src/ +├── _warm-theme.scss # NEW: All theme variables +├── _colors.scss # Keep for backward compatibility, import warm-theme +├── styles.scss # Updated to use warm theme +└── app/ + └── ... +``` + +### Migration Steps per Component +1. Import warm theme variables +2. Replace hardcoded colors with variables +3. Update shadows to use warm shadow system +4. Update border-radius to use consistent values +5. Update typography to use font variables +6. Test component in isolation +7. Test component in context + +--- + +## 7. Testing Checklist + +### Visual Testing +- [ ] Light/dark mode appearance (if applicable) +- [ ] Mobile responsiveness +- [ ] Hover/focus states +- [ ] Active/disabled states +- [ ] Loading states + +### Accessibility +- [ ] Color contrast ratios (WCAG AA minimum) +- [ ] Focus indicators visible +- [ ] Text remains readable at 200% zoom + +### Browser Testing +- [ ] Chrome +- [ ] Safari + +--- + +## 8. Timeline Estimate + +| Phase | Components | Estimated Time | +|-------|------------|----------------| +| Phase 1: Foundation + Dark Mode | 5 files + service | 3-4 hours | +| Phase 2: Shared | 21 files | 3-4 hours | +| Phase 3: Home | 6 files | 1-2 hours | +| Phase 4: Salaries | 29 files | 4-5 hours | +| Phase 5: Companies | 5 files | 1-2 hours | +| Phase 6: Interviews | 9 files | 2 hours | +| Phase 7: Users | 1 file | 30 min | +| Phase 8: Admin | 15 files | 2-3 hours | +| Phase 9: App-Level | 2 files | 30 min | +| **Total** | **93 files + service** | **~18-24 hours** | + +--- + +## 9. Rollback Plan + +If issues arise: +1. All changes will be in separate commits per phase +2. Original `_colors.scss` will be preserved +3. CSS variables in `:root` allow easy theme switching +4. Each phase can be reverted independently + +--- + +## 10. Dark/Light Mode Feature + +### 10.1 Overview + +The application will support both light and dark modes with: +- **System preference detection** via `prefers-color-scheme` +- **Manual toggle** for user preference override +- **Persistence** via localStorage +- **Smooth transitions** between modes + +### 10.2 Dark Mode Color Palette + +#### Backgrounds (Warm Dark Tones) +| Variable | Light Mode | Dark Mode | Usage | +|----------|------------|-----------|-------| +| `--warm-bg-primary` | `#fffbf7` | `#1a1816` | Main page background | +| `--warm-bg-secondary` | `#fef7f0` | `#231f1d` | Alternating sections | +| `--warm-bg-tertiary` | `#fff9f2` | `#2a2523` | Card backgrounds | +| `--warm-bg-accent` | `#fdf4e8` | `#332d2a` | Highlighted areas | +| `--warm-bg-elevated` | `#ffffff` | `#3a3331` | Elevated cards, modals | + +#### Text Colors (Comfortable for Dark Backgrounds) +| Variable | Light Mode | Dark Mode | Usage | +|----------|------------|-----------|-------| +| `--warm-text-primary` | `#3d3d3d` | `#e8e4e1` | Body text | +| `--warm-text-secondary` | `#5c5c5c` | `#b8b2ad` | Secondary text | +| `--warm-text-muted` | `#7a7a7a` | `#8a8480` | Captions, hints | +| `--warm-text-heading` | `#2d2d2d` | `#f5f2f0` | Headings | + +#### Accent Colors (Adjusted for Dark Mode Contrast) +| Variable | Light Mode | Dark Mode | Usage | +|----------|------------|-----------|-------| +| `--warm-accent-primary` | `#e07a5f` | `#e8937b` | Primary (Terracotta - lightened) | +| `--warm-accent-secondary` | `#81b29a` | `#95c4ad` | Secondary (Sage - lightened) | +| `--warm-accent-tertiary` | `#f2cc8f` | `#f5d9a8` | Highlights (Gold - lightened) | +| `--warm-accent-soft` | `#f4a261` | `#f7b57d` | CTAs (Orange - lightened) | + +#### Semantic Colors (Dark Mode Adjusted) +| Variable | Light Mode | Dark Mode | Usage | +|----------|------------|-----------|-------| +| `--warm-danger` | `#d95550` | `#e57373` | Errors | +| `--warm-warning` | `#e9a820` | `#f5bc4c` | Warnings | +| `--warm-success` | `#81b29a` | `#95c4ad` | Success | +| `--warm-info` | `#6fa8c7` | `#8fc1db` | Information | + +#### Borders & Dividers +| Variable | Light Mode | Dark Mode | Usage | +|----------|------------|-----------|-------| +| `--warm-border-color` | `rgba(61,45,35,0.12)` | `rgba(255,251,247,0.08)` | Subtle borders | +| `--warm-border-strong` | `rgba(61,45,35,0.20)` | `rgba(255,251,247,0.15)` | Prominent borders | +| `--warm-divider` | `rgba(61,45,35,0.08)` | `rgba(255,251,247,0.06)` | Section dividers | + +#### Shadows (Dark Mode) +| Variable | Light Mode | Dark Mode | +|----------|------------|-----------| +| `--warm-shadow-color` | `rgba(61,45,35,0.08)` | `rgba(0,0,0,0.25)` | +| `--warm-shadow-color-medium` | `rgba(61,45,35,0.12)` | `rgba(0,0,0,0.35)` | +| `--warm-shadow-color-strong` | `rgba(61,45,35,0.16)` | `rgba(0,0,0,0.45)` | + +#### Special Dark Mode Colors +| Variable | Value | Usage | +|----------|-------|-------| +| `--warm-dark-glow` | `rgba(232,147,123,0.15)` | Subtle accent glow effects | +| `--warm-dark-overlay` | `rgba(0,0,0,0.5)` | Modal overlays | +| `--warm-dark-highlight` | `rgba(255,251,247,0.05)` | Hover highlights | + +### 10.3 Implementation Strategy + +#### CSS Variables Structure +```scss +// _warm-theme.scss + +:root { + // Light mode (default) + --warm-bg-primary: #fffbf7; + --warm-bg-secondary: #fef7f0; + --warm-bg-tertiary: #fff9f2; + --warm-bg-accent: #fdf4e8; + --warm-bg-elevated: #ffffff; + + --warm-text-primary: #3d3d3d; + --warm-text-secondary: #5c5c5c; + --warm-text-muted: #7a7a7a; + --warm-text-heading: #2d2d2d; + + --warm-accent-primary: #e07a5f; + --warm-accent-secondary: #81b29a; + --warm-accent-tertiary: #f2cc8f; + --warm-accent-soft: #f4a261; + + --warm-danger: #d95550; + --warm-warning: #e9a820; + --warm-success: #81b29a; + --warm-info: #6fa8c7; + + --warm-border-color: rgba(61, 45, 35, 0.12); + --warm-border-strong: rgba(61, 45, 35, 0.20); + --warm-divider: rgba(61, 45, 35, 0.08); + + --warm-shadow-color: rgba(61, 45, 35, 0.08); + --warm-shadow-color-medium: rgba(61, 45, 35, 0.12); + --warm-shadow-color-strong: rgba(61, 45, 35, 0.16); + + // Transition for smooth theme switching + --theme-transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; +} + +// Dark mode - System preference +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --warm-bg-primary: #1a1816; + --warm-bg-secondary: #231f1d; + --warm-bg-tertiary: #2a2523; + --warm-bg-accent: #332d2a; + --warm-bg-elevated: #3a3331; + + --warm-text-primary: #e8e4e1; + --warm-text-secondary: #b8b2ad; + --warm-text-muted: #8a8480; + --warm-text-heading: #f5f2f0; + + --warm-accent-primary: #e8937b; + --warm-accent-secondary: #95c4ad; + --warm-accent-tertiary: #f5d9a8; + --warm-accent-soft: #f7b57d; + + --warm-danger: #e57373; + --warm-warning: #f5bc4c; + --warm-success: #95c4ad; + --warm-info: #8fc1db; + + --warm-border-color: rgba(255, 251, 247, 0.08); + --warm-border-strong: rgba(255, 251, 247, 0.15); + --warm-divider: rgba(255, 251, 247, 0.06); + + --warm-shadow-color: rgba(0, 0, 0, 0.25); + --warm-shadow-color-medium: rgba(0, 0, 0, 0.35); + --warm-shadow-color-strong: rgba(0, 0, 0, 0.45); + } +} + +// Dark mode - Manual override +:root[data-theme="dark"] { + --warm-bg-primary: #1a1816; + --warm-bg-secondary: #231f1d; + --warm-bg-tertiary: #2a2523; + --warm-bg-accent: #332d2a; + --warm-bg-elevated: #3a3331; + + --warm-text-primary: #e8e4e1; + --warm-text-secondary: #b8b2ad; + --warm-text-muted: #8a8480; + --warm-text-heading: #f5f2f0; + + --warm-accent-primary: #e8937b; + --warm-accent-secondary: #95c4ad; + --warm-accent-tertiary: #f5d9a8; + --warm-accent-soft: #f7b57d; + + --warm-danger: #e57373; + --warm-warning: #f5bc4c; + --warm-success: #95c4ad; + --warm-info: #8fc1db; + + --warm-border-color: rgba(255, 251, 247, 0.08); + --warm-border-strong: rgba(255, 251, 247, 0.15); + --warm-divider: rgba(255, 251, 247, 0.06); + + --warm-shadow-color: rgba(0, 0, 0, 0.25); + --warm-shadow-color-medium: rgba(0, 0, 0, 0.35); + --warm-shadow-color-strong: rgba(0, 0, 0, 0.45); +} +``` + +#### Theme Toggle Component +```typescript +// theme-toggle.service.ts +@Injectable({ providedIn: 'root' }) +export class ThemeService { + private readonly THEME_KEY = 'preferred-theme'; + + currentTheme$ = new BehaviorSubject<'light' | 'dark' | 'system'>('system'); + + constructor() { + this.initTheme(); + } + + private initTheme(): void { + const saved = localStorage.getItem(this.THEME_KEY) as 'light' | 'dark' | 'system'; + if (saved) { + this.setTheme(saved); + } + } + + setTheme(theme: 'light' | 'dark' | 'system'): void { + const root = document.documentElement; + + if (theme === 'system') { + root.removeAttribute('data-theme'); + } else { + root.setAttribute('data-theme', theme); + } + + localStorage.setItem(this.THEME_KEY, theme); + this.currentTheme$.next(theme); + } + + toggleTheme(): void { + const current = this.currentTheme$.value; + const next = current === 'light' ? 'dark' : 'light'; + this.setTheme(next); + } +} +``` + +#### Theme Toggle Button (Navbar) +```html + + + + +``` + +### 10.4 Dark Mode Chart Colors + +```typescript +// Chart colors for dark mode +const chartColorsDark = { + primary: '#e8937b', // Terracotta (lightened) + secondary: '#95c4ad', // Sage (lightened) + tertiary: '#f5d9a8', // Gold (lightened) + quaternary: '#f7b57d', // Orange (lightened) + quinary: '#8fc1db', // Soft blue (lightened) + senary: '#d4b8e0', // Soft purple (lightened) +}; + +// Grid and axis styling for dark mode +const gridColorDark = 'rgba(255, 251, 247, 0.06)'; +const textColorDark = '#b8b2ad'; +``` + +### 10.5 Dark Mode Specific Adjustments + +#### Images +```scss +// Reduce image brightness slightly in dark mode for comfort +[data-theme="dark"] img:not(.no-dim), +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) img:not(.no-dim) { + filter: brightness(0.9); + } +} +``` + +#### Code Blocks / Data Tables +```scss +[data-theme="dark"], +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --code-bg: #2a2523; + --code-text: #e8e4e1; + --table-stripe-bg: rgba(255, 251, 247, 0.03); + --table-hover-bg: rgba(255, 251, 247, 0.06); + } +} +``` + +#### Focus States +```scss +// Ensure visible focus states in dark mode +[data-theme="dark"], +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --focus-ring-color: rgba(232, 147, 123, 0.5); + + *:focus-visible { + outline: 2px solid var(--warm-accent-primary); + outline-offset: 2px; + } + } +} +``` + +### 10.6 Phase Integration + +Dark mode implementation should be integrated into **Phase 1: Foundation** alongside the base theme setup: + +**Updated Phase 1 Tasks:** +- [ ] Create `src/_warm-theme.scss` with CSS variables for both modes +- [ ] Add theme toggle service +- [ ] Add theme toggle button to navbar +- [ ] Update `styles.scss` to use CSS variables +- [ ] Add smooth transition effects +- [ ] Test system preference detection +- [ ] Test manual toggle persistence + +### 10.7 Accessibility Considerations + +| Requirement | Light Mode | Dark Mode | +|-------------|------------|-----------| +| Text contrast (body) | 7.5:1 ✓ | 8.2:1 ✓ | +| Text contrast (muted) | 4.8:1 ✓ | 4.5:1 ✓ | +| Link contrast | 4.6:1 ✓ | 5.1:1 ✓ | +| Focus indicators | Visible ✓ | Visible ✓ | + +All color combinations meet WCAG AA standards (4.5:1 minimum for normal text). + +--- + +## 11. Future Considerations + +### Additional Themes +The CSS variable architecture allows for easy addition of: +- High contrast mode +- Sepia/reading mode +- Custom user themes + +### Scheduled Theme Switching +Could implement automatic switching based on time of day (e.g., dark mode after 8 PM). + +--- + +## Approval + +**Please review this plan and confirm:** +1. Is the color palette acceptable? +2. Is the phase prioritization correct? +3. Any modules/components to skip? +4. Any additional requirements? + +Once approved, I will begin implementation starting with Phase 1. diff --git a/src/_warm-theme.scss b/src/_warm-theme.scss new file mode 100644 index 00000000..7082f86d --- /dev/null +++ b/src/_warm-theme.scss @@ -0,0 +1,432 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// WARM & FRIENDLY THEME +// A refined, comfortable design system optimized for long reading sessions +// Supports both light and dark modes with smooth transitions +// ═══════════════════════════════════════════════════════════════════════════════ + +// ───────────────────────────────────────────────────────────────────────────────── +// CSS CUSTOM PROPERTIES (CSS Variables) +// These enable runtime theme switching without page reload +// ───────────────────────────────────────────────────────────────────────────────── + +:root { + // ═══════════════════════════════════════════════════════════════════════════════ + // LIGHT MODE (Default) + // Warm paper tones with terracotta and sage accents + // ═══════════════════════════════════════════════════════════════════════════════ + + // Backgrounds - Warm paper tones + --warm-bg-primary: #fffbf7; + --warm-bg-secondary: #fef7f0; + --warm-bg-tertiary: #fff9f2; + --warm-bg-accent: #fdf4e8; + --warm-bg-elevated: #ffffff; + --warm-bg-inverse: #3d3d3d; + + // Text - Warm grays for reduced eye strain + --warm-text-primary: #3d3d3d; + --warm-text-secondary: #5c5c5c; + --warm-text-muted: #7a7a7a; + --warm-text-heading: #2d2d2d; + --warm-text-inverse: #f5f5f5; + + // Accent colors - Warm, friendly palette + --warm-accent-primary: #e07a5f; // Terracotta + --warm-accent-primary-hover: #c96a52; + --warm-accent-secondary: #81b29a; // Sage + --warm-accent-secondary-hover: #6fa089; + --warm-accent-tertiary: #f2cc8f; // Gold + --warm-accent-soft: #f4a261; // Orange + + // Semantic colors + --warm-danger: #d95550; + --warm-danger-bg: #fef2f2; + --warm-warning: #e9a820; + --warm-warning-bg: #fffbeb; + --warm-success: #81b29a; + --warm-success-bg: #f0fdf4; + --warm-info: #6fa8c7; + --warm-info-bg: #eff6ff; + + // Borders & Dividers + --warm-border-color: rgba(61, 45, 35, 0.12); + --warm-border-strong: rgba(61, 45, 35, 0.2); + --warm-border-focus: rgba(224, 122, 95, 0.5); + --warm-divider: rgba(61, 45, 35, 0.08); + + // Shadows - Warm-tinted for cohesion + --warm-shadow-color: rgba(61, 45, 35, 0.08); + --warm-shadow-color-medium: rgba(61, 45, 35, 0.12); + --warm-shadow-color-strong: rgba(61, 45, 35, 0.16); + + // Component-specific + --warm-input-bg: #ffffff; + --warm-input-border: rgba(61, 45, 35, 0.2); + --warm-input-focus-border: #e07a5f; + --warm-input-focus-ring: rgba(224, 122, 95, 0.25); + + --warm-card-bg: #ffffff; + --warm-card-border: rgba(61, 45, 35, 0.08); + + --warm-modal-overlay: rgba(45, 45, 45, 0.5); + --warm-modal-bg: #ffffff; + + --warm-navbar-bg: #fffbf7; + --warm-navbar-border: rgba(61, 45, 35, 0.1); + + --warm-footer-bg: #3d3d3d; + --warm-footer-text: #f5f5f5; + --warm-footer-muted: #c0c0c0; + + // Chart colors + --warm-chart-1: #e07a5f; + --warm-chart-2: #81b29a; + --warm-chart-3: #f2cc8f; + --warm-chart-4: #f4a261; + --warm-chart-5: #6fa8c7; + --warm-chart-6: #c4a1d4; + --warm-chart-grid: rgba(61, 45, 35, 0.08); + --warm-chart-text: #5c5c5c; + + // Code/monospace + --warm-code-bg: #fef7f0; + --warm-code-text: #3d3d3d; + --warm-code-border: rgba(61, 45, 35, 0.12); + + // Table + --warm-table-stripe: rgba(61, 45, 35, 0.03); + --warm-table-hover: rgba(61, 45, 35, 0.06); + --warm-table-border: rgba(61, 45, 35, 0.1); + + // Scrollbar + --warm-scrollbar-track: #fef7f0; + --warm-scrollbar-thumb: rgba(61, 45, 35, 0.2); + --warm-scrollbar-thumb-hover: rgba(61, 45, 35, 0.35); + + // Transitions + --theme-transition: + background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, + box-shadow 0.3s ease, fill 0.3s ease, stroke 0.3s ease; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// DARK MODE - System Preference Detection +// Automatically activates when user's OS is set to dark mode +// Can be overridden by manual [data-theme="light"] attribute +// ═══════════════════════════════════════════════════════════════════════════════ + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + // Backgrounds - Warm dark tones (not pure black) + --warm-bg-primary: #1a1816; + --warm-bg-secondary: #231f1d; + --warm-bg-tertiary: #2a2523; + --warm-bg-accent: #332d2a; + --warm-bg-elevated: #3a3331; + --warm-bg-inverse: #f5f2f0; + + // Text - Comfortable for dark backgrounds + --warm-text-primary: #e8e4e1; + --warm-text-secondary: #b8b2ad; + --warm-text-muted: #8a8480; + --warm-text-heading: #f5f2f0; + --warm-text-inverse: #1a1816; + + // Accent colors - Lightened for dark mode contrast + --warm-accent-primary: #e8937b; + --warm-accent-primary-hover: #f0a892; + --warm-accent-secondary: #95c4ad; + --warm-accent-secondary-hover: #a8d4be; + --warm-accent-tertiary: #f5d9a8; + --warm-accent-soft: #f7b57d; + + // Semantic colors - Adjusted for dark backgrounds + --warm-danger: #e57373; + --warm-danger-bg: rgba(229, 115, 115, 0.15); + --warm-warning: #f5bc4c; + --warm-warning-bg: rgba(245, 188, 76, 0.15); + --warm-success: #95c4ad; + --warm-success-bg: rgba(149, 196, 173, 0.15); + --warm-info: #8fc1db; + --warm-info-bg: rgba(143, 193, 219, 0.15); + + // Borders & Dividers + --warm-border-color: rgba(255, 251, 247, 0.08); + --warm-border-strong: rgba(255, 251, 247, 0.15); + --warm-border-focus: rgba(232, 147, 123, 0.5); + --warm-divider: rgba(255, 251, 247, 0.06); + + // Shadows - Darker for dark mode + --warm-shadow-color: rgba(0, 0, 0, 0.25); + --warm-shadow-color-medium: rgba(0, 0, 0, 0.35); + --warm-shadow-color-strong: rgba(0, 0, 0, 0.45); + + // Component-specific + --warm-input-bg: #2a2523; + --warm-input-border: rgba(255, 251, 247, 0.15); + --warm-input-focus-border: #e8937b; + --warm-input-focus-ring: rgba(232, 147, 123, 0.25); + + --warm-card-bg: #2a2523; + --warm-card-border: rgba(255, 251, 247, 0.06); + + --warm-modal-overlay: rgba(0, 0, 0, 0.7); + --warm-modal-bg: #2a2523; + + --warm-navbar-bg: #1a1816; + --warm-navbar-border: rgba(255, 251, 247, 0.08); + + --warm-footer-bg: #131211; + --warm-footer-text: #e8e4e1; + --warm-footer-muted: #8a8480; + + // Chart colors - Lightened for visibility + --warm-chart-1: #e8937b; + --warm-chart-2: #95c4ad; + --warm-chart-3: #f5d9a8; + --warm-chart-4: #f7b57d; + --warm-chart-5: #8fc1db; + --warm-chart-6: #d4b8e0; + --warm-chart-grid: rgba(255, 251, 247, 0.06); + --warm-chart-text: #b8b2ad; + + // Code/monospace + --warm-code-bg: #2a2523; + --warm-code-text: #e8e4e1; + --warm-code-border: rgba(255, 251, 247, 0.08); + + // Table + --warm-table-stripe: rgba(255, 251, 247, 0.03); + --warm-table-hover: rgba(255, 251, 247, 0.06); + --warm-table-border: rgba(255, 251, 247, 0.08); + + // Scrollbar + --warm-scrollbar-track: #231f1d; + --warm-scrollbar-thumb: rgba(255, 251, 247, 0.15); + --warm-scrollbar-thumb-hover: rgba(255, 251, 247, 0.25); + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// DARK MODE - Manual Override +// Activates when user explicitly selects dark mode via toggle +// Takes precedence over system preference +// ═══════════════════════════════════════════════════════════════════════════════ + +:root[data-theme="dark"] { + // Backgrounds + --warm-bg-primary: #1a1816; + --warm-bg-secondary: #231f1d; + --warm-bg-tertiary: #2a2523; + --warm-bg-accent: #332d2a; + --warm-bg-elevated: #3a3331; + --warm-bg-inverse: #f5f2f0; + + // Text + --warm-text-primary: #e8e4e1; + --warm-text-secondary: #b8b2ad; + --warm-text-muted: #8a8480; + --warm-text-heading: #f5f2f0; + --warm-text-inverse: #1a1816; + + // Accents + --warm-accent-primary: #e8937b; + --warm-accent-primary-hover: #f0a892; + --warm-accent-secondary: #95c4ad; + --warm-accent-secondary-hover: #a8d4be; + --warm-accent-tertiary: #f5d9a8; + --warm-accent-soft: #f7b57d; + + // Semantic + --warm-danger: #e57373; + --warm-danger-bg: rgba(229, 115, 115, 0.15); + --warm-warning: #f5bc4c; + --warm-warning-bg: rgba(245, 188, 76, 0.15); + --warm-success: #95c4ad; + --warm-success-bg: rgba(149, 196, 173, 0.15); + --warm-info: #8fc1db; + --warm-info-bg: rgba(143, 193, 219, 0.15); + + // Borders + --warm-border-color: rgba(255, 251, 247, 0.08); + --warm-border-strong: rgba(255, 251, 247, 0.15); + --warm-border-focus: rgba(232, 147, 123, 0.5); + --warm-divider: rgba(255, 251, 247, 0.06); + + // Shadows + --warm-shadow-color: rgba(0, 0, 0, 0.25); + --warm-shadow-color-medium: rgba(0, 0, 0, 0.35); + --warm-shadow-color-strong: rgba(0, 0, 0, 0.45); + + // Components + --warm-input-bg: #2a2523; + --warm-input-border: rgba(255, 251, 247, 0.15); + --warm-input-focus-border: #e8937b; + --warm-input-focus-ring: rgba(232, 147, 123, 0.25); + + --warm-card-bg: #2a2523; + --warm-card-border: rgba(255, 251, 247, 0.06); + + --warm-modal-overlay: rgba(0, 0, 0, 0.7); + --warm-modal-bg: #2a2523; + + --warm-navbar-bg: #1a1816; + --warm-navbar-border: rgba(255, 251, 247, 0.08); + + --warm-footer-bg: #131211; + --warm-footer-text: #e8e4e1; + --warm-footer-muted: #8a8480; + + // Charts + --warm-chart-1: #e8937b; + --warm-chart-2: #95c4ad; + --warm-chart-3: #f5d9a8; + --warm-chart-4: #f7b57d; + --warm-chart-5: #8fc1db; + --warm-chart-6: #d4b8e0; + --warm-chart-grid: rgba(255, 251, 247, 0.06); + --warm-chart-text: #b8b2ad; + + // Code + --warm-code-bg: #2a2523; + --warm-code-text: #e8e4e1; + --warm-code-border: rgba(255, 251, 247, 0.08); + + // Table + --warm-table-stripe: rgba(255, 251, 247, 0.03); + --warm-table-hover: rgba(255, 251, 247, 0.06); + --warm-table-border: rgba(255, 251, 247, 0.08); + + // Scrollbar + --warm-scrollbar-track: #231f1d; + --warm-scrollbar-thumb: rgba(255, 251, 247, 0.15); + --warm-scrollbar-thumb-hover: rgba(255, 251, 247, 0.25); +} + +// ───────────────────────────────────────────────────────────────────────────────── +// SCSS VARIABLES (for use in component stylesheets) +// These provide SCSS-level access to commonly used values +// ───────────────────────────────────────────────────────────────────────────────── + +// Typography +$font-heading: "Lora", Georgia, serif; +$font-body: + "Nunito", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; +$font-mono: "JetBrains Mono", "Fira Code", Consolas, monospace; + +// Border radius +$radius-sm: 6px; +$radius-md: 10px; +$radius-lg: 16px; +$radius-xl: 24px; +$radius-full: 9999px; + +// Spacing scale +$space-1: 0.25rem; // 4px +$space-2: 0.5rem; // 8px +$space-3: 0.75rem; // 12px +$space-4: 1rem; // 16px +$space-5: 1.5rem; // 24px +$space-6: 2rem; // 32px +$space-7: 2.5rem; // 40px +$space-8: 3rem; // 48px +$space-9: 4rem; // 64px +$space-10: 5rem; // 80px + +// Transitions +$transition-fast: 0.15s ease; +$transition-normal: 0.2s ease; +$transition-slow: 0.3s ease; + +// Shadow definitions (using CSS variables) +$shadow-sm: + 0 1px 2px var(--warm-shadow-color), + 0 2px 4px var(--warm-shadow-color); + +$shadow-md: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color); + +$shadow-lg: + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color), + 0 16px 32px var(--warm-shadow-color-medium); + +$shadow-xl: + 0 8px 16px var(--warm-shadow-color), + 0 16px 32px var(--warm-shadow-color-medium), + 0 32px 64px var(--warm-shadow-color-strong); + +// ───────────────────────────────────────────────────────────────────────────────── +// GLOBAL STYLES +// Base styles that apply to the entire application +// ───────────────────────────────────────────────────────────────────────────────── + +// Smooth theme transitions +*, +*::before, +*::after { + transition: var(--theme-transition); +} + +// Disable transitions on page load to prevent flash +.no-transitions, +.no-transitions *, +.no-transitions *::before, +.no-transitions *::after { + transition: none !important; +} + +// Custom scrollbar styling +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-track { + background: var(--warm-scrollbar-track); +} + +::-webkit-scrollbar-thumb { + background: var(--warm-scrollbar-thumb); + border-radius: $radius-full; + border: 2px solid var(--warm-scrollbar-track); + + &:hover { + background: var(--warm-scrollbar-thumb-hover); + } +} + +// Firefox scrollbar +* { + scrollbar-width: thin; + scrollbar-color: var(--warm-scrollbar-thumb) var(--warm-scrollbar-track); +} + +// Focus styles +:focus-visible { + outline: 2px solid var(--warm-accent-primary); + outline-offset: 2px; +} + +// Selection styling +::selection { + background-color: var(--warm-accent-primary); + color: white; +} + +// Dark mode image adjustment +[data-theme="dark"] img:not(.no-dim):not(.logo-image) { + filter: brightness(0.92); +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) img:not(.no-dim):not(.logo-image) { + filter: brightness(0.92); + } +} diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29b..0387d889 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,86 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// APP ROOT COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; + flex: 1 0 auto; +} + +.app-container { + display: flex; + flex-direction: column; + min-height: 100vh; + background-color: var(--warm-bg-primary); +} + +.main-content { + flex: 1; + padding-top: 1rem; + padding-bottom: 2rem; +} + +// Page transition animations +.page-enter { + opacity: 0; + transform: translateY(10px); +} + +.page-enter-active { + opacity: 1; + transform: translateY(0); + transition: + opacity 0.3s ease, + transform 0.3s ease; +} + +// Global loading overlay +.global-loading { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 251, 247, 0.9); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + + .loading-content { + text-align: center; + + .spinner { + width: 48px; + height: 48px; + border: 3px solid var(--warm-border-color); + border-top-color: var(--warm-accent-primary); + border-radius: 50%; + animation: spin 1s linear infinite; + } + + .loading-text { + margin-top: 1rem; + color: var(--warm-text-secondary); + font-size: 0.875rem; + } + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +// Toast notifications container +.toast-container { + position: fixed; + bottom: 1.5rem; + right: 1.5rem; + z-index: 9998; + display: flex; + flex-direction: column; + gap: 0.75rem; +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 378254e3..79535232 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -14,9 +14,7 @@ export class AppComponent implements OnInit, OnDestroy { return this.router.url.startsWith("/admin"); } - constructor( - private readonly router: Router, - ) {} + constructor(private readonly router: Router) {} ngOnInit(): void {} diff --git a/src/app/components/admin-navbar/admin-navbar.component.html b/src/app/components/admin-navbar/admin-navbar.component.html index bc648776..41b1ef9e 100644 --- a/src/app/components/admin-navbar/admin-navbar.component.html +++ b/src/app/components/admin-navbar/admin-navbar.component.html @@ -1,6 +1,4 @@ - + Админка {{ link.title }} + + Techinterview.space @@ -24,7 +23,7 @@ class="collapse navbar-collapse justify-content-end" id="navbarSupportedContent" > - + - + + + + + + + + {{ loginName }} - + Мой профиль Мои теги - + + + Сбросить + + Добавить компанию + - - - Добавить компанию - - - 0; else nothingToShow" diff --git a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.scss b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.scss index e69de29b..e65d5a0d 100644 --- a/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.scss +++ b/src/app/modules/admin/components/companies/companies-admin-page/companies-admin-page.component.scss @@ -0,0 +1,98 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// COMPANIES ADMIN PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.companies-admin { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 1.5rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.companies-table-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + overflow: hidden; +} + +.table { + margin-bottom: 0; + + thead { + background-color: var(--warm-bg-tertiary); + + th { + color: var(--warm-text-heading); + font-weight: 600; + border-bottom: 2px solid var(--warm-border-color); + padding: 1rem; + } + } + + tbody { + td { + color: var(--warm-text-primary); + padding: 0.875rem 1rem; + border-bottom: 1px solid var(--warm-border-color); + vertical-align: middle; + } + + tr { + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } + } + + tr:last-child td { + border-bottom: none; + } + } +} + +.company-link { + color: var(--warm-accent-primary); + text-decoration: none; + font-weight: 500; + + &:hover { + color: var(--warm-accent-primary-hover); + text-decoration: underline; + } +} + +.search-box { + margin-bottom: 1rem; + + input { + background-color: var(--warm-bg-tertiary); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.75rem 1rem; + color: var(--warm-text-primary); + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } +} diff --git a/src/app/modules/admin/components/companies/company-admin-page/company-admin-page.component.scss b/src/app/modules/admin/components/companies/company-admin-page/company-admin-page.component.scss index e69de29b..65fade1e 100644 --- a/src/app/modules/admin/components/companies/company-admin-page/company-admin-page.component.scss +++ b/src/app/modules/admin/components/companies/company-admin-page/company-admin-page.component.scss @@ -0,0 +1,90 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// COMPANY ADMIN PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.company-admin { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 1.5rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.company-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + padding: 1.5rem; + margin-bottom: 1.5rem; + + h3, + h4 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 1rem; + } +} + +.info-row { + display: flex; + margin-bottom: 0.75rem; + + .label { + font-weight: 600; + color: var(--warm-text-secondary); + min-width: 150px; + } + + .value { + color: var(--warm-text-primary); + } +} + +.action-buttons { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; + margin-top: 1.5rem; +} + +.reviews-section { + margin-top: 2rem; + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 1rem; + } +} + +.review-item { + background-color: var(--warm-bg-tertiary); + border-radius: 10px; + padding: 1.25rem; + margin-bottom: 1rem; + border: 1px solid var(--warm-border-color); + + .review-header { + display: flex; + justify-content: space-between; + margin-bottom: 0.75rem; + color: var(--warm-text-muted); + font-size: 0.875rem; + } + + .review-content { + color: var(--warm-text-primary); + line-height: 1.6; + } +} diff --git a/src/app/modules/admin/components/companies/reviews-to-approve/reviews-to-approve-page.component.scss b/src/app/modules/admin/components/companies/reviews-to-approve/reviews-to-approve-page.component.scss index e69de29b..c17eda71 100644 --- a/src/app/modules/admin/components/companies/reviews-to-approve/reviews-to-approve-page.component.scss +++ b/src/app/modules/admin/components/companies/reviews-to-approve/reviews-to-approve-page.component.scss @@ -0,0 +1,95 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// REVIEWS TO APPROVE PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.reviews-to-approve { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 1.5rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } + + .badge { + background-color: var(--warm-accent-primary); + color: white; + padding: 0.35rem 0.75rem; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 600; + margin-left: 0.75rem; + } +} + +.review-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + padding: 1.5rem; + margin-bottom: 1.5rem; + transition: box-shadow 0.2s ease; + + &:hover { + box-shadow: var(--warm-shadow-lg); + } + + .review-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; + + .company-name { + font-family: var(--warm-font-heading); + font-weight: 600; + color: var(--warm-text-heading); + font-size: 1.125rem; + } + + .review-date { + color: var(--warm-text-muted); + font-size: 0.875rem; + } + } + + .review-content { + color: var(--warm-text-primary); + line-height: 1.6; + margin-bottom: 1.25rem; + } + + .review-actions { + display: flex; + gap: 0.75rem; + padding-top: 1rem; + border-top: 1px solid var(--warm-border-color); + } +} + +.empty-state { + text-align: center; + padding: 4rem 2rem; + color: var(--warm-text-muted); + + i { + font-size: 4rem; + margin-bottom: 1rem; + opacity: 0.5; + } + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + } +} diff --git a/src/app/modules/admin/components/currencies-page/currencies-page.component.scss b/src/app/modules/admin/components/currencies-page/currencies-page.component.scss index 790d927a..55603459 100644 --- a/src/app/modules/admin/components/currencies-page/currencies-page.component.scss +++ b/src/app/modules/admin/components/currencies-page/currencies-page.component.scss @@ -1,12 +1,97 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// CURRENCIES PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.currencies-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 1.5rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.currencies-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + padding: 1.5rem; +} + .currencies-json { max-height: 400px; overflow-y: auto; font-size: 0.85rem; white-space: pre-wrap; word-break: break-all; + background-color: var(--warm-code-bg, #2a2523); + color: var(--warm-code-text, #e8e4e1); + padding: 1rem; + border-radius: 8px; + font-family: var(--warm-font-mono); } code { font-size: 0.8rem; + background-color: var(--warm-bg-tertiary); + color: var(--warm-accent-primary); + padding: 0.125rem 0.375rem; + border-radius: 4px; + font-family: var(--warm-font-mono); } +.table { + margin-bottom: 0; + + thead { + background-color: var(--warm-bg-tertiary); + + th { + color: var(--warm-text-heading); + font-weight: 600; + border-bottom: 2px solid var(--warm-border-color); + padding: 1rem; + } + } + + tbody { + td { + color: var(--warm-text-primary); + padding: 0.875rem 1rem; + border-bottom: 1px solid var(--warm-border-color); + vertical-align: middle; + } + + tr { + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } + } + + tr:last-child td { + border-bottom: none; + } + } +} + +.currency-code { + font-family: var(--warm-font-mono); + font-weight: 600; + color: var(--warm-accent-primary); +} + +.currency-rate { + font-family: var(--warm-font-mono); +} diff --git a/src/app/modules/admin/components/currencies-page/currency-record-edit-form.ts b/src/app/modules/admin/components/currencies-page/currency-record-edit-form.ts index da8880f4..e3970b49 100644 --- a/src/app/modules/admin/components/currencies-page/currency-record-edit-form.ts +++ b/src/app/modules/admin/components/currencies-page/currency-record-edit-form.ts @@ -6,7 +6,10 @@ export class CurrencyRecordEditForm extends FormGroup { constructor() { super({ currency: new FormControl(CurrencyType.USD, [Validators.required]), - value: new FormControl(null, [Validators.required, Validators.min(0.0001)]), + value: new FormControl(null, [ + Validators.required, + Validators.min(0.0001), + ]), pubDate: new FormControl(new Date().toISOString().split("T")[0], [ Validators.required, ]), diff --git a/src/app/modules/admin/components/generate-qr-code-page/generate-qr-page.component.scss b/src/app/modules/admin/components/generate-qr-code-page/generate-qr-page.component.scss index e69de29b..52f3317e 100644 --- a/src/app/modules/admin/components/generate-qr-code-page/generate-qr-page.component.scss +++ b/src/app/modules/admin/components/generate-qr-code-page/generate-qr-page.component.scss @@ -0,0 +1,83 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// GENERATE QR CODE PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.qr-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 1.5rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.qr-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + padding: 2rem; + max-width: 500px; +} + +.form-group { + margin-bottom: 1.5rem; + + label { + display: block; + color: var(--warm-text-secondary); + font-weight: 500; + margin-bottom: 0.5rem; + } + + input, + textarea { + width: 100%; + background-color: var(--warm-bg-tertiary); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.75rem 1rem; + color: var(--warm-text-primary); + font-size: 1rem; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } +} + +.qr-preview { + margin-top: 2rem; + padding: 1.5rem; + background-color: var(--warm-bg-tertiary); + border-radius: 10px; + text-align: center; + + img, + canvas { + max-width: 100%; + border-radius: 8px; + } +} + +.download-btn { + margin-top: 1rem; +} diff --git a/src/app/modules/admin/components/health-check-table/health-check-table.component.scss b/src/app/modules/admin/components/health-check-table/health-check-table.component.scss index c4511414..4910df0e 100644 --- a/src/app/modules/admin/components/health-check-table/health-check-table.component.scss +++ b/src/app/modules/admin/components/health-check-table/health-check-table.component.scss @@ -1,3 +1,54 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// HEALTH CHECK TABLE COMPONENT STYLES +// Warm & Friendly Theme +// Component-specific styles only (global styles in styles.scss) +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .clickable tr { cursor: pointer; + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } +} + +.health-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + overflow: hidden; +} + +.table { + margin-bottom: 0; + + thead { + background-color: var(--warm-bg-tertiary); + + th { + color: var(--warm-text-heading); + font-weight: 600; + border-bottom: 2px solid var(--warm-border-color); + padding: 1rem; + } + } + + tbody { + td { + color: var(--warm-text-primary); + padding: 0.875rem 1rem; + border-bottom: 1px solid var(--warm-border-color); + vertical-align: middle; + } + + tr:last-child td { + border-bottom: none; + } + } } diff --git a/src/app/modules/admin/components/jobs-table/jobs-table.component.scss b/src/app/modules/admin/components/jobs-table/jobs-table.component.scss index c4511414..cd0f937d 100644 --- a/src/app/modules/admin/components/jobs-table/jobs-table.component.scss +++ b/src/app/modules/admin/components/jobs-table/jobs-table.component.scss @@ -1,3 +1,76 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// JOBS TABLE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .clickable tr { cursor: pointer; + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } +} + +.table { + background-color: var(--warm-card-bg); + border-radius: 10px; + overflow: hidden; + + thead { + background-color: var(--warm-bg-tertiary); + + th { + color: var(--warm-text-heading); + font-weight: 600; + border-bottom: 2px solid var(--warm-border-color); + padding: 1rem; + } + } + + tbody { + td { + color: var(--warm-text-primary); + padding: 0.875rem 1rem; + border-bottom: 1px solid var(--warm-border-color); + vertical-align: middle; + } + + tr:last-child td { + border-bottom: none; + } + } +} + +.job-status { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + + &.status-running { + background-color: rgba(129, 178, 154, 0.15); + color: var(--warm-success); + } + + &.status-failed { + background-color: rgba(217, 85, 80, 0.15); + color: var(--warm-danger); + } + + &.status-pending { + background-color: rgba(242, 204, 143, 0.2); + color: var(--warm-warning); + } + + &.status-completed { + background-color: rgba(129, 178, 154, 0.15); + color: var(--warm-success); + } } diff --git a/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.html b/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.html index 2524b444..e1173b71 100644 --- a/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.html +++ b/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.html @@ -18,7 +18,6 @@ Тип Промпт - Модель Движок Активен Создан @@ -30,11 +29,10 @@ {{ item.type }} - + {{ item.prompt }} - {{ item.model }} {{ item.engine }} {{ item.isActive ? "Да" : "Нет" }} diff --git a/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.scss b/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.scss index 04cf19e4..aefcec85 100644 --- a/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.scss +++ b/src/app/modules/admin/components/openai-prompts/openai-prompts-admin-page.component.scss @@ -1,6 +1,88 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// OPENAI PROMPTS ADMIN PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.prompts-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 1.5rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.prompts-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + overflow: hidden; +} + // Component-specific styles for OpenAI Prompts admin page .text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + max-width: 300px; +} + +.table { + margin-bottom: 0; + + thead { + background-color: var(--warm-bg-tertiary); + + th { + color: var(--warm-text-heading); + font-weight: 600; + border-bottom: 2px solid var(--warm-border-color); + padding: 1rem; + } + } + + tbody { + td { + color: var(--warm-text-primary); + padding: 0.875rem 1rem; + border-bottom: 1px solid var(--warm-border-color); + vertical-align: middle; + } + + tr { + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } + } + + tr:last-child td { + border-bottom: none; + } + } +} + +.prompt-preview { + background-color: var(--warm-bg-tertiary); + padding: 0.75rem; + border-radius: 6px; + font-size: 0.875rem; + color: var(--warm-text-secondary); + max-height: 80px; + overflow: hidden; +} + +.prompt-actions { + display: flex; + gap: 0.5rem; } diff --git a/src/app/modules/admin/components/salaries/historical-data-templates/historical-data-templates.component.html b/src/app/modules/admin/components/salaries/historical-data-templates/historical-data-templates.component.html index 5d12cecd..81c00e69 100644 --- a/src/app/modules/admin/components/salaries/historical-data-templates/historical-data-templates.component.html +++ b/src/app/modules/admin/components/salaries/historical-data-templates/historical-data-templates.component.html @@ -27,7 +27,9 @@ - ...{{ item.id.split('-').pop() }} + ...{{ item.id.split("-").pop() }} {{ item.name }} @@ -35,11 +37,23 @@ *ngIf="item.professionIds && item.professionIds.length > 0" href="#" class="text-primary" - (click)="openProfessionsModal(item.professionIds); $event.preventDefault()" + (click)=" + openProfessionsModal(item.professionIds); + $event.preventDefault() + " > - {{ item.professionIds.length }} специальност{{ item.professionIds.length === 1 ? 'ь' : (item.professionIds.length < 5 ? 'и' : 'ей') }} + {{ item.professionIds.length }} специальност{{ + item.professionIds.length === 1 + ? "ь" + : item.professionIds.length < 5 + ? "и" + : "ей" + }} - - + - {{ item.createdAt | date: "yyyy-MM-dd HH:mm" }} {{ item.updatedAt | date: "yyyy-MM-dd HH:mm" }} @@ -63,7 +77,9 @@ Удалить все записи @@ -84,7 +100,10 @@ - 0"> + 0" + > 0" href="#" class="text-primary" - (click)="openProfessionsModal(item.professionIds); $event.preventDefault()" + (click)=" + openProfessionsModal(item.professionIds); + $event.preventDefault() + " > - {{ item.professionIds.length }} специальност{{ item.professionIds.length === 1 ? 'ь' : (item.professionIds.length < 5 ? 'и' : 'ей') }} + {{ item.professionIds.length }} специальност{{ + item.professionIds.length === 1 + ? "ь" + : item.professionIds.length < 5 + ? "и" + : "ей" + }} - - + - {{ item.telegramChatId }} {{ item.createdAt | date: "yyyy-MM-dd HH:mm" }} diff --git a/src/app/modules/admin/components/telegram/job-posting-message-subscriptions/job-posting-message-subscriptions.component.scss b/src/app/modules/admin/components/telegram/job-posting-message-subscriptions/job-posting-message-subscriptions.component.scss index 19a4b3f7..758ce770 100644 --- a/src/app/modules/admin/components/telegram/job-posting-message-subscriptions/job-posting-message-subscriptions.component.scss +++ b/src/app/modules/admin/components/telegram/job-posting-message-subscriptions/job-posting-message-subscriptions.component.scss @@ -1,4 +1,20 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// JOB POSTING MESSAGE SUBSCRIPTIONS COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .open-api-json { + background-color: var(--warm-code-bg, #2a2523); + padding: 1rem; + border-radius: 8px; + font-size: 0.875rem; + line-height: 1.5; + color: var(--warm-code-text, #e8e4e1); + font-family: var(--warm-font-mono); white-space: pre-wrap; max-height: 500px; overflow-y: auto; @@ -13,6 +29,43 @@ .modal-content { overflow: visible; } + + .ng-select { + .ng-select-container { + background-color: var(--warm-bg-tertiary); + border-color: var(--warm-border-color); + border-radius: 8px; + + &:hover { + border-color: var(--warm-accent-primary); + } + } + + &.ng-select-focused .ng-select-container { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + } + + .ng-dropdown-panel { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + box-shadow: var(--warm-shadow-lg); + } + + .ng-option { + color: var(--warm-text-primary); + + &.ng-option-marked { + background-color: var(--warm-bg-accent); + } + + &.ng-option-selected { + background-color: var(--warm-accent-primary); + color: white; + } + } + } } // Ensure action dropdown is visible outside table container @@ -22,5 +75,53 @@ .dropdown-menu { position: absolute; z-index: 1050; + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + box-shadow: var(--warm-shadow-lg); + } +} + +.subscriptions-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + overflow: hidden; +} + +.table { + margin-bottom: 0; + + thead { + background-color: var(--warm-bg-tertiary); + + th { + color: var(--warm-text-heading); + font-weight: 600; + border-bottom: 2px solid var(--warm-border-color); + padding: 1rem; + } + } + + tbody { + td { + color: var(--warm-text-primary); + padding: 0.875rem 1rem; + border-bottom: 1px solid var(--warm-border-color); + vertical-align: middle; + } + + tr { + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } + } + + tr:last-child td { + border-bottom: none; + } } } diff --git a/src/app/modules/admin/components/telegram/reviews-stat-subscriptions/reviews-stat-subscriptions.component.scss b/src/app/modules/admin/components/telegram/reviews-stat-subscriptions/reviews-stat-subscriptions.component.scss index b470acdf..2ed6a29c 100644 --- a/src/app/modules/admin/components/telegram/reviews-stat-subscriptions/reviews-stat-subscriptions.component.scss +++ b/src/app/modules/admin/components/telegram/reviews-stat-subscriptions/reviews-stat-subscriptions.component.scss @@ -1,13 +1,75 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// REVIEWS STAT SUBSCRIPTIONS COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .open-api-json { - background-color: #202020; + background-color: var(--warm-code-bg, #2a2523); padding: 1rem; - border-radius: 0.25rem; + border-radius: 8px; font-size: 0.875rem; line-height: 1.5; - color: #ededed; + color: var(--warm-code-text, #e8e4e1); + font-family: var(--warm-font-mono); + overflow-x: auto; } .dropdown-item { cursor: pointer; font-weight: 500; + color: var(--warm-text-primary); + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + color: var(--warm-accent-primary); + } +} + +.subscriptions-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + overflow: hidden; +} + +.table { + margin-bottom: 0; + + thead { + background-color: var(--warm-bg-tertiary); + + th { + color: var(--warm-text-heading); + font-weight: 600; + border-bottom: 2px solid var(--warm-border-color); + padding: 1rem; + } + } + + tbody { + td { + color: var(--warm-text-primary); + padding: 0.875rem 1rem; + border-bottom: 1px solid var(--warm-border-color); + vertical-align: middle; + } + + tr { + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } + } + + tr:last-child td { + border-bottom: none; + } + } } diff --git a/src/app/modules/admin/components/telegram/salaries-stat-subscriptions/stat-data-cache-records.component.html b/src/app/modules/admin/components/telegram/salaries-stat-subscriptions/stat-data-cache-records.component.html index 5d739875..b2203ade 100644 --- a/src/app/modules/admin/components/telegram/salaries-stat-subscriptions/stat-data-cache-records.component.html +++ b/src/app/modules/admin/components/telegram/salaries-stat-subscriptions/stat-data-cache-records.component.html @@ -38,11 +38,23 @@ *ngIf="item.professionIds && item.professionIds.length > 0" href="#" class="text-primary" - (click)="openProfessionsModal(item.professionIds); $event.preventDefault()" + (click)=" + openProfessionsModal(item.professionIds); + $event.preventDefault() + " > - {{ item.professionIds.length }} специальност{{ item.professionIds.length === 1 ? 'ь' : (item.professionIds.length < 5 ? 'и' : 'ей') }} + {{ item.professionIds.length }} специальност{{ + item.professionIds.length === 1 + ? "ь" + : item.professionIds.length < 5 + ? "и" + : "ей" + }} - - + - Send Test Email +Отправка тестового письма - - Test Resend Integration - - From * + От * Test Resend Integration placeholder="sender@yourdomain.com" required /> - Must be a verified domain in Resend + Домен должен быть подтвержден в Resend - To * + Кому * Test Resend Integration - Subject * + Тема * Test Resend Integration - HTML Body * + HTML текст * Test Resend Integration placeholder="Hello!This is a test email." required > - Enter HTML content for the email body + HTML текст письма @@ -75,10 +80,13 @@ Test Resend Integration [disabled]="isSending || !isFormValid()" > - - Sending... + + Отправка... - Send Email + Отправить diff --git a/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.scss b/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.scss index b0570d27..48e00d66 100644 --- a/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.scss +++ b/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.scss @@ -1 +1,105 @@ -// Styles for send-email-page component +// ═══════════════════════════════════════════════════════════════════════════════ +// SEND EMAIL PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.email-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 1.5rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.email-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + padding: 2rem; + max-width: 700px; +} + +.form-group { + margin-bottom: 1.5rem; + + label { + display: block; + color: var(--warm-text-secondary); + font-weight: 500; + margin-bottom: 0.5rem; + } + + input, + textarea, + select { + width: 100%; + background-color: var(--warm-bg-tertiary); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.75rem 1rem; + color: var(--warm-text-primary); + font-size: 1rem; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } + + textarea { + min-height: 200px; + resize: vertical; + } +} + +.form-hint { + font-size: 0.875rem; + color: var(--warm-text-muted); + margin-top: 0.375rem; +} + +.email-preview { + margin-top: 2rem; + padding: 1.5rem; + background-color: var(--warm-bg-tertiary); + border-radius: 10px; + border: 1px solid var(--warm-border-color); + + h4 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 1rem; + } + + .preview-content { + background-color: var(--warm-card-bg); + padding: 1rem; + border-radius: 6px; + border: 1px solid var(--warm-border-color); + } +} + +.form-actions { + margin-top: 2rem; + display: flex; + gap: 1rem; + justify-content: flex-end; +} diff --git a/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.ts b/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.ts index 53a0def4..2dd5d5fe 100644 --- a/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.ts +++ b/src/app/modules/admin/components/tools/send-email-page/send-email-page.component.ts @@ -1,6 +1,9 @@ import { Component, OnDestroy } from "@angular/core"; import { TitleService } from "@services/title.service"; -import { AdminEmailService, SendCustomEmailRequest } from "@services/admin-email.service"; +import { + AdminEmailService, + SendCustomEmailRequest, +} from "@services/admin-email.service"; import { AlertService } from "@shared/components/alert/services/alert.service"; import { untilDestroyed } from "@shared/subscriptions/until-destroyed"; @@ -50,7 +53,9 @@ export class SendEmailPageComponent implements OnDestroy { }, error: (err) => { this.isSending = false; - this.alertService.error("Failed to send email: " + (err.message || "Unknown error")); + this.alertService.error( + "Failed to send email: " + (err.message || "Unknown error"), + ); }, }); } diff --git a/src/app/modules/admin/components/users/users-admin-page/users-admin-page.component.html b/src/app/modules/admin/components/users/users-admin-page/users-admin-page.component.html index 2f68b46d..d64a9b11 100644 --- a/src/app/modules/admin/components/users/users-admin-page/users-admin-page.component.html +++ b/src/app/modules/admin/components/users/users-admin-page/users-admin-page.component.html @@ -33,12 +33,12 @@ Фильтры - + Применить Сброс diff --git a/src/app/modules/companies/components/add-review-page/add-review-page.component.scss b/src/app/modules/companies/components/add-review-page/add-review-page.component.scss index 4c899c69..976820fa 100644 --- a/src/app/modules/companies/components/add-review-page/add-review-page.component.scss +++ b/src/app/modules/companies/components/add-review-page/add-review-page.component.scss @@ -1,3 +1,218 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// ADD REVIEW PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.add-review-page { + padding: 1.5rem 0; + max-width: 800px; + margin: 0 auto; +} + +.page-header { + margin-bottom: 2rem; + text-align: center; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .page-description { + color: var(--warm-text-secondary); + } +} + +.review-form-card { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); +} + .first-step-text { font-size: 1.2rem; + color: var(--warm-text-primary); + line-height: 1.6; +} + +.step-indicator { + display: flex; + justify-content: center; + gap: 1rem; + margin-bottom: 2rem; + + .step { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--warm-text-muted); + font-size: 0.875rem; + + .step-number { + width: 28px; + height: 28px; + border-radius: 50%; + background-color: var(--warm-bg-tertiary); + border: 2px solid var(--warm-border-color); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 0.875rem; + } + + &.active { + color: var(--warm-accent-primary); + + .step-number { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + } + } + + &.completed { + color: var(--warm-success); + + .step-number { + background-color: var(--warm-success); + border-color: var(--warm-success); + color: white; + } + } + } + + .step-connector { + width: 40px; + height: 2px; + background-color: var(--warm-border-color); + align-self: center; + + &.completed { + background-color: var(--warm-success); + } + } +} + +.form-section { + margin-bottom: 2rem; + + .section-title { + font-family: var(--warm-font-heading); + font-size: 1.125rem; + color: var(--warm-text-heading); + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--warm-border-color); + } +} + +.form-group { + margin-bottom: 1.5rem; + + label { + display: block; + color: var(--warm-text-secondary); + font-weight: 500; + margin-bottom: 0.5rem; + } + + input, + textarea, + select { + width: 100%; + background-color: var(--warm-bg-tertiary); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.75rem 1rem; + color: var(--warm-text-primary); + font-size: 1rem; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } + + textarea { + min-height: 150px; + resize: vertical; + } +} + +.rating-input { + display: flex; + gap: 0.5rem; + + .star { + font-size: 1.5rem; + cursor: pointer; + color: var(--warm-border-color); + transition: + color 0.15s ease, + transform 0.15s ease; + + &:hover { + transform: scale(1.1); + } + + &.filled { + color: var(--warm-accent-tertiary); + } + } +} + +.form-hint { + font-size: 0.875rem; + color: var(--warm-text-muted); + margin-top: 0.375rem; +} + +.form-actions { + margin-top: 2rem; + display: flex; + gap: 1rem; + justify-content: flex-end; + padding-top: 1.5rem; + border-top: 1px solid var(--warm-border-color); +} + +.guidelines-card { + background-color: var(--warm-bg-accent); + border-radius: 10px; + padding: 1.25rem; + margin-bottom: 2rem; + + h4 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + font-size: 1rem; + margin-bottom: 0.75rem; + } + + ul { + margin: 0; + padding-left: 1.25rem; + color: var(--warm-text-secondary); + font-size: 0.875rem; + + li { + margin-bottom: 0.375rem; + } + } } diff --git a/src/app/modules/companies/components/companies-page/companies-page.component.scss b/src/app/modules/companies/components/companies-page/companies-page.component.scss index fd409aa3..5e4737d2 100644 --- a/src/app/modules/companies/components/companies-page/companies-page.component.scss +++ b/src/app/modules/companies/components/companies-page/companies-page.component.scss @@ -1,29 +1,144 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// COMPANIES PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.companies-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 2rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .page-description { + color: var(--warm-text-secondary); + font-size: 1.1rem; + } +} + +.search-section { + margin-bottom: 2rem; + + .search-input { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-border-color); + border-radius: 10px; + padding: 0.875rem 1.25rem; + font-size: 1rem; + color: var(--warm-text-primary); + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } +} + +.company-list { + padding: 1rem 1.5rem; +} + .company-list-item { - transition: all 0.3s ease; + transition: all 0.25s ease; cursor: pointer; - border-radius: 10px; - padding-left: 24px; - padding-right: 24px; - padding-top: 16px; - padding-bottom: 16px; - border: 1px solid #e0e0e0; - color: rgb(33, 37, 41); + border-radius: 12px; + padding: 1.25rem 1.5rem; + border: 1px solid var(--warm-border-color); + background-color: var(--warm-card-bg); + color: var(--warm-text-primary); + box-shadow: var(--warm-shadow-sm); &:hover { - transform: scale(1.05); - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + transform: translateY(-2px); + box-shadow: var(--warm-shadow-lg); + border-color: var(--warm-accent-primary); } -} -.company-list { - padding: 16px 24px; + .company-name { + font-family: var(--warm-font-heading); + font-weight: 600; + color: var(--warm-text-heading); + font-size: 1.125rem; + margin-bottom: 0.25rem; + } + + .company-info { + color: var(--warm-text-secondary); + font-size: 0.875rem; + } + + .company-rating { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.5rem; + + .rating-value { + font-weight: 600; + color: var(--warm-accent-primary); + } + + .review-count { + color: var(--warm-text-muted); + font-size: 0.875rem; + } + } } .tg-link { - color: rgb(33, 37, 41); + color: var(--warm-text-primary); text-decoration: none; + transition: color 0.2s ease; &:hover { + color: var(--warm-accent-primary); text-decoration: underline; } } + +.empty-state { + text-align: center; + padding: 4rem 2rem; + color: var(--warm-text-muted); + + i { + font-size: 4rem; + margin-bottom: 1rem; + opacity: 0.5; + } + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + } + + p { + max-width: 400px; + margin: 0 auto; + } +} + +.pagination-wrapper { + margin-top: 2rem; + display: flex; + justify-content: center; +} diff --git a/src/app/modules/companies/components/companies-page/companies-page.component.ts b/src/app/modules/companies/components/companies-page/companies-page.component.ts index 0d50a995..8a058527 100644 --- a/src/app/modules/companies/components/companies-page/companies-page.component.ts +++ b/src/app/modules/companies/components/companies-page/companies-page.component.ts @@ -42,12 +42,12 @@ export class CompaniesPageComponent implements OnInit, OnDestroy { private readonly route: ActivatedRoute, private readonly gtag: GoogleAnalyticsService, ) { - this.metaTagService.setPageMetaTags( "Отзывы об IT компаниях", "Отзывы об IT компаниях в Казахстане", "/companies", - "https://techinterview.fra1.cdn.digitaloceanspaces.com/images/company_reviews_1000.png"); + "https://techinterview.fra1.cdn.digitaloceanspaces.com/images/company_reviews_1000.png", + ); } ngOnInit(): void { diff --git a/src/app/modules/companies/components/company-page/company-page.component.scss b/src/app/modules/companies/components/company-page/company-page.component.scss index 987ae98d..3315484f 100644 --- a/src/app/modules/companies/components/company-page/company-page.component.scss +++ b/src/app/modules/companies/components/company-page/company-page.component.scss @@ -1,25 +1,176 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// COMPANY PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.company-page { + padding: 1.5rem 0; +} + +.company-header { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + margin-bottom: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + + .company-name { + font-family: var(--warm-font-heading); + font-size: 2rem; + font-weight: 700; + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .company-description { + color: var(--warm-text-secondary); + line-height: 1.6; + margin-bottom: 1rem; + } + + .company-meta { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + color: var(--warm-text-muted); + font-size: 0.875rem; + + .meta-item { + display: flex; + align-items: center; + gap: 0.5rem; + + i { + color: var(--warm-accent-secondary); + } + } + } +} + +.total-rating-section { + background-color: var(--warm-bg-accent); + border-radius: 12px; + padding: 1.5rem; + text-align: center; + margin-bottom: 2rem; + + .rating-value { + font-family: var(--warm-font-heading); + font-size: 3rem; + font-weight: 700; + color: var(--warm-accent-primary); + line-height: 1; + } + + .rating-label { + color: var(--warm-text-secondary); + font-size: 0.875rem; + margin-top: 0.5rem; + } +} + .total-rating-link { cursor: pointer; text-decoration: none; + color: var(--warm-accent-primary); + transition: color 0.2s ease; + &:hover { + color: var(--warm-accent-soft); text-decoration: underline; } } +.reviews-section { + margin-top: 2rem; + + .section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + + h2 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + font-size: 1.5rem; + } + } +} + .review-awaiting-approval-label { - font-weight: bold; - color: #b88a00; + display: inline-flex; + align-items: center; + gap: 0.375rem; + font-weight: 600; + color: var(--warm-warning); font-style: italic; + font-size: 0.875rem; + background-color: rgba(233, 168, 32, 0.1); + padding: 0.25rem 0.75rem; + border-radius: 20px; } .review-outdated-label { - font-weight: bold; - color: #3c3c3c; + display: inline-flex; + align-items: center; + gap: 0.375rem; + font-weight: 600; + color: var(--warm-text-muted); font-style: italic; + font-size: 0.875rem; + background-color: var(--warm-bg-tertiary); + padding: 0.25rem 0.75rem; + border-radius: 20px; } .review-left-by-me-label { - font-weight: bold; - color: #092a00; + display: inline-flex; + align-items: center; + gap: 0.375rem; + font-weight: 600; + color: var(--warm-success); font-style: italic; + font-size: 0.875rem; + background-color: rgba(129, 178, 154, 0.1); + padding: 0.25rem 0.75rem; + border-radius: 20px; +} + +.add-review-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.empty-reviews { + text-align: center; + padding: 3rem 2rem; + background-color: var(--warm-card-bg); + border-radius: 12px; + border: 1px dashed var(--warm-border-color); + + i { + font-size: 3rem; + color: var(--warm-text-muted); + opacity: 0.5; + margin-bottom: 1rem; + } + + h4 { + font-family: var(--warm-font-heading); + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + } + + p { + color: var(--warm-text-muted); + max-width: 350px; + margin: 0 auto 1.5rem; + } } diff --git a/src/app/modules/companies/components/company-review-block/company-review-block.component.scss b/src/app/modules/companies/components/company-review-block/company-review-block.component.scss index fdcd9b9a..77833310 100644 --- a/src/app/modules/companies/components/company-review-block/company-review-block.component.scss +++ b/src/app/modules/companies/components/company-review-block/company-review-block.component.scss @@ -1,26 +1,196 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// COMPANY REVIEW BLOCK COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.review-block { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1rem; + border: 1px solid var(--warm-border-color); + box-shadow: var(--warm-shadow-sm); + transition: box-shadow 0.2s ease; + + &:hover { + box-shadow: var(--warm-shadow-md); + } +} + +.review-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; + + .reviewer-info { + .reviewer-position { + font-weight: 500; + color: var(--warm-text-heading); + margin-bottom: 0.25rem; + } + + .review-date { + color: var(--warm-text-muted); + font-size: 0.8125rem; + } + } + + .review-rating { + display: flex; + align-items: center; + gap: 0.375rem; + + .stars { + color: var(--warm-accent-tertiary); + } + + .rating-value { + font-weight: 600; + color: var(--warm-accent-primary); + font-size: 0.9375rem; + } + } +} + +.review-content { + color: var(--warm-text-primary); + line-height: 1.7; + margin-bottom: 1rem; + + .review-section { + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } + + .section-label { + font-weight: 600; + color: var(--warm-text-heading); + font-size: 0.875rem; + margin-bottom: 0.375rem; + } + + .section-content { + color: var(--warm-text-secondary); + } + } +} + +.review-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 1rem; + border-top: 1px solid var(--warm-border-color); +} + .usefullness-rate { cursor: pointer; - transition: color 0.3s ease; - font-size: 12px; + transition: color 0.2s ease; + font-size: 0.8125rem; font-weight: 600; font-style: italic; + color: var(--warm-text-muted); &:hover { - color: #000000; + color: var(--warm-accent-primary); text-decoration: underline; text-underline-offset: 3px; } + + &.positive { + color: var(--warm-success); + } + + &.negative { + color: var(--warm-danger); + } } .i-work-here { - font-size: 12px; + display: inline-flex; + align-items: center; + gap: 0.375rem; + font-size: 0.8125rem; font-weight: 500; font-style: italic; + color: var(--warm-accent-secondary); cursor: pointer; + transition: color 0.2s ease; &:hover { - color: #000000; + color: var(--warm-text-heading); text-decoration: underline; text-underline-offset: 3px; } + + i { + font-size: 0.875rem; + } +} + +.review-actions { + display: flex; + gap: 0.75rem; + + .action-btn { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.75rem; + border-radius: 6px; + font-size: 0.8125rem; + color: var(--warm-text-muted); + background-color: transparent; + border: 1px solid var(--warm-border-color); + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: var(--warm-bg-accent); + color: var(--warm-text-primary); + border-color: var(--warm-accent-primary); + } + + &.active { + background-color: var(--warm-accent-primary); + color: white; + border-color: var(--warm-accent-primary); + } + } +} + +.review-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 1rem; + + .tag { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.625rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 500; + background-color: var(--warm-bg-accent); + color: var(--warm-text-secondary); + + &.positive { + background-color: rgba(129, 178, 154, 0.15); + color: var(--warm-success); + } + + &.negative { + background-color: rgba(217, 85, 80, 0.15); + color: var(--warm-danger); + } + } } diff --git a/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.scss b/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.scss index c7de9230..bf651c60 100644 --- a/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.scss +++ b/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.scss @@ -1,16 +1,175 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// RECENT REVIEWS PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.recent-reviews-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 2rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .page-description { + color: var(--warm-text-secondary); + } +} + +.reviews-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + .review-list-item { - transition: all 0.3s ease; - border-radius: 10px; - padding-left: 24px; - padding-right: 24px; - padding-top: 16px; - padding-bottom: 16px; - border: 1px solid #e0e0e0; - color: rgb(33, 37, 41); - background-color: #fff; + transition: all 0.25s ease; + border-radius: 12px; + padding: 1.25rem 1.5rem; + border: 1px solid var(--warm-border-color); + background-color: var(--warm-card-bg); + color: var(--warm-text-primary); + box-shadow: var(--warm-shadow-sm); &:hover { - transform: scale(1.05); - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + transform: translateY(-2px); + box-shadow: var(--warm-shadow-lg); + border-color: var(--warm-accent-primary); + } + + .review-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.75rem; + + .company-name { + font-family: var(--warm-font-heading); + font-weight: 600; + color: var(--warm-text-heading); + font-size: 1.125rem; + + &:hover { + color: var(--warm-accent-primary); + } + } + + .review-date { + color: var(--warm-text-muted); + font-size: 0.875rem; + } + } + + .review-rating { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.75rem; + + .stars { + color: var(--warm-accent-tertiary); + } + + .rating-value { + font-weight: 600; + color: var(--warm-accent-primary); + } } + + .review-excerpt { + color: var(--warm-text-secondary); + font-size: 0.9375rem; + line-height: 1.6; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .review-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 1rem; + padding-top: 0.75rem; + border-top: 1px solid var(--warm-border-color); + + .review-meta { + display: flex; + gap: 1rem; + color: var(--warm-text-muted); + font-size: 0.8125rem; + } + + .read-more { + color: var(--warm-accent-primary); + font-weight: 500; + font-size: 0.875rem; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } +} + +.filter-section { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.25rem; + margin-bottom: 1.5rem; + border: 1px solid var(--warm-border-color); + + .filter-label { + color: var(--warm-text-secondary); + font-weight: 500; + margin-bottom: 0.5rem; + } + + select { + background-color: var(--warm-bg-tertiary); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.5rem 1rem; + color: var(--warm-text-primary); + + &:focus { + border-color: var(--warm-accent-primary); + outline: none; + } + } +} + +.empty-state { + text-align: center; + padding: 4rem 2rem; + color: var(--warm-text-muted); + + i { + font-size: 4rem; + margin-bottom: 1rem; + opacity: 0.5; + } + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + } +} + +.pagination-wrapper { + margin-top: 2rem; + display: flex; + justify-content: center; } diff --git a/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.ts b/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.ts index 60082ac3..e05ef600 100644 --- a/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.ts +++ b/src/app/modules/companies/components/recent-reviews/recent-reviews-page.component.ts @@ -38,7 +38,8 @@ export class RecentReviewsPageComponent implements OnInit, OnDestroy { "Отзывы об IT компаниях", "Отзывы об IT компаниях в Казахстане", "/companies/recent-reviews", - "https://techinterview.fra1.cdn.digitaloceanspaces.com/images/company_reviews_1000.png"); + "https://techinterview.fra1.cdn.digitaloceanspaces.com/images/company_reviews_1000.png", + ); } ngOnInit(): void { diff --git a/src/app/modules/home/components/about-us/about-us.component.scss b/src/app/modules/home/components/about-us/about-us.component.scss index 28bd8fe8..a8f60718 100644 --- a/src/app/modules/home/components/about-us/about-us.component.scss +++ b/src/app/modules/home/components/about-us/about-us.component.scss @@ -1,8 +1,180 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// ABOUT US COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +// Link styling .link-without-underline { text-decoration: none; - color: inherit; + color: var(--warm-accent-primary); + transition: color 0.2s ease; &:hover { text-decoration: underline; + color: var(--warm-accent-primary-hover); + } +} + +// Card styling overrides +.card { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: 16px; + box-shadow: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color); + transition: + box-shadow 0.3s ease, + background-color 0.3s ease; + + &:hover { + box-shadow: + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color), + 0 16px 32px var(--warm-shadow-color-medium); + } +} + +.card-body { + color: var(--warm-text-primary); +} + +// Headings +.h1 { + font-family: "Lora", Georgia, serif; + color: var(--warm-text-heading); + font-weight: 600; + margin-bottom: 1rem; +} + +.h3 { + font-family: "Lora", Georgia, serif; + color: var(--warm-text-heading); + font-weight: 600; +} + +.h5 { + font-weight: 600; + color: var(--warm-accent-primary); +} + +// Text styling +.text-muted { + color: var(--warm-text-muted) !important; +} + +.text-reset { + color: var(--warm-accent-primary) !important; +} + +// List styling +ul { + padding-left: 1.5rem; + margin-top: 0.75rem; + + li { + margin-bottom: 0.5rem; + color: var(--warm-text-secondary); + line-height: 1.7; + } +} + +// Images +.img-fluid.rounded { + border-radius: 12px; + box-shadow: + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color); + transition: + transform 0.3s ease, + box-shadow 0.3s ease; +} + +// Interactive image hover +.increasable-on-hover { + cursor: pointer; + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.03); + + .img-fluid.rounded { + box-shadow: + 0 8px 16px var(--warm-shadow-color), + 0 16px 32px var(--warm-shadow-color-medium); + } + } +} + +// Sticker images +.row-cols-4 { + .col { + .img-fluid.rounded { + background-color: var(--warm-bg-secondary); + border: 1px solid var(--warm-card-border); + } + } +} + +// Horizontal rule +hr { + border-color: var(--warm-divider); + opacity: 1; + margin: 2rem 0; +} + +// Icon styling +.bi { + color: var(--warm-accent-primary); +} + +// Contact info section +.bi-geo, +.bi-github, +.bi-telegram, +.bi-envelope { + color: var(--warm-accent-secondary); + font-size: 1.1rem; +} + +// Italic text for quotes/references +.fst-italic { + color: var(--warm-accent-primary); +} + +// Bold text +.fw-bold { + color: var(--warm-text-heading); +} + +// Small text +.small { + font-size: 0.875rem; + color: var(--warm-text-muted); +} + +// Responsive adjustments +@media (max-width: 768px) { + .card-body { + padding: 1.5rem !important; + } + + .h1 { + font-size: 1.75rem; + } + + .h3 { + font-size: 1.35rem; + } + + .row-cols-4 { + .col { + flex: 0 0 50%; + max-width: 50%; + } } } diff --git a/src/app/modules/home/components/about-us/about-us.component.ts b/src/app/modules/home/components/about-us/about-us.component.ts index d3c37e2b..edcaf24e 100644 --- a/src/app/modules/home/components/about-us/about-us.component.ts +++ b/src/app/modules/home/components/about-us/about-us.component.ts @@ -13,14 +13,14 @@ interface IContributor { standalone: false, }) export class AboutUsComponent implements OnDestroy { - constructor(private readonly metaTagService: MetaTagService) { this.metaTagService.setPageMetaTags( "О проекте Techinterview.space", "История и миссия проекта Techinterview.space. Контрибьюторы, стикеры, контакты.", "/about-us", null, - false); + false, + ); } ngOnDestroy(): void { diff --git a/src/app/modules/home/components/error-page-component/error-page-component.component.scss b/src/app/modules/home/components/error-page-component/error-page-component.component.scss index 37007ac4..b1ba998d 100644 --- a/src/app/modules/home/components/error-page-component/error-page-component.component.scss +++ b/src/app/modules/home/components/error-page-component/error-page-component.component.scss @@ -1,8 +1,124 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// ERROR PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.error-container { + min-height: 60vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem 1rem; + text-align: center; +} + .error-title { + font-family: "Lora", Georgia, serif; font-size: 5rem; - font-weight: 500; + font-weight: 600; + color: var(--warm-accent-primary); + margin-bottom: 1rem; + line-height: 1; +} + +.error-subtitle { + font-family: "Lora", Georgia, serif; + font-size: 1.75rem; + font-weight: 600; + color: var(--warm-text-heading); + margin-bottom: 1rem; +} + +.error-message { + font-size: 1.1rem; + color: var(--warm-text-secondary); + max-width: 500px; + margin: 0 auto 2rem; + line-height: 1.6; } .img-fluid { max-width: 350px; + border-radius: 16px; + filter: drop-shadow(0 8px 24px var(--warm-shadow-color-medium)); + margin-bottom: 2rem; +} + +// Action buttons +.error-actions { + display: flex; + gap: 1rem; + flex-wrap: wrap; + justify-content: center; + margin-top: 1.5rem; +} + +.btn-error-primary { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + padding: 0.75rem 1.5rem; + border-radius: 10px; + font-weight: 600; + transition: all 0.2s ease; + + &:hover { + background-color: var(--warm-accent-primary-hover); + border-color: var(--warm-accent-primary-hover); + transform: translateY(-2px); + box-shadow: + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color); + } +} + +.btn-error-outline { + background-color: transparent; + border: 2px solid var(--warm-border-strong); + color: var(--warm-text-primary); + padding: 0.75rem 1.5rem; + border-radius: 10px; + font-weight: 600; + transition: all 0.2s ease; + + &:hover { + background-color: var(--warm-bg-accent); + border-color: var(--warm-accent-primary); + color: var(--warm-accent-primary); + } +} + +// Responsive +@media (max-width: 768px) { + .error-title { + font-size: 3.5rem; + } + + .error-subtitle { + font-size: 1.35rem; + } + + .error-message { + font-size: 1rem; + } + + .img-fluid { + max-width: 250px; + } + + .error-actions { + flex-direction: column; + align-items: center; + + .btn-error-primary, + .btn-error-outline { + width: 100%; + max-width: 280px; + } + } } diff --git a/src/app/modules/home/components/home/home.component.html b/src/app/modules/home/components/home/home.component.html index 2456885f..a993316d 100644 --- a/src/app/modules/home/components/home/home.component.html +++ b/src/app/modules/home/components/home/home.component.html @@ -19,8 +19,8 @@ - - Зарплаты в IT + + Зарплаты в IT Здесь мы собираем и анализируем данные по зарплатам в IT в Казахстане. @@ -41,7 +41,7 @@ - - Отзывы к компаниям в IT + + + Отзывы к компаниям в IT + Поработал в компании и тебе повезло очуться в команде дружелюбных профессионалов? Поделись этим с другими, чтобы и они знали, куда @@ -72,7 +74,7 @@ - - Заметки к техническим интервью + + + Заметки к техническим интервью + Если ты проводишь технические собеседования и хочешь создать шаблон и сохранять фидбеки, чтобы потом делиться ими с коллегами — @@ -102,7 +106,7 @@ - Обновлено: 2024-04-21 - Дата вступления в силу: 2024-04-21 + Обновлено: 2025-01-20 + Дата вступления в силу: 2025-01-20 1. Общие положения @@ -16,10 +16,26 @@ 2. Предмет соглашения - Сайт Techinterview.space предоставляет Пользователю возможность - внесения информации о зарплатных данных в целях формирования - статистической информации. Информация, предоставляемая Пользователем, - должна быть достоверной и актуальной. + Сайт Techinterview.space предоставляет Пользователю следующие + возможности: + + + Зарплаты в IT — внесение информации о зарплатных + данных в целях формирования статистической информации по рынку + труда в IT-сфере Казахстана. + + + Отзывы о компаниях — публикация и просмотр + отзывов о работодателях в IT-сфере. + + + Заметки к техническим интервью — создание + шаблонов для проведения технических собеседований и сохранение + фидбеков по кандидатам. + + + Информация, предоставляемая Пользователем, должна быть достоверной и + актуальной. 3. Права и обязанности сторон @@ -34,6 +50,10 @@ Запрашивать информацию у Администрации касательно обработки его данных. + + Удалить свои данные (зарплатные записи, отзывы, заметки к + интервью) через личный кабинет или по запросу в Администрацию. + @@ -44,8 +64,16 @@ При использовании Сайта предоставлять достоверную или близкую к достоверности информацию. + + Не публиковать оскорбительные, клеветнические или заведомо ложные + отзывы о компаниях. + + + Не разглашать конфиденциальную информацию о кандидатах, полученную + в ходе интервью. + - + Работодатели часто пишут в трудовых договорах и соглашениях, что разглашать свою зарплату работник не имеет права. Мы не собираем личную информацию, имен, фамилий и места работы, потому данные о @@ -71,6 +99,10 @@ Обеспечивать вывод обезличенной информации на Сайте таким образом, чтобы никто не смог скомпрометировать Пользователя. + + Модерировать отзывы о компаниях и удалять контент, нарушающий + данное Соглашение. + @@ -79,14 +111,24 @@ 4.1. Сайт не собирает email адреса, телефонные номера и личные - данные Пользователей. + данные Пользователей напрямую. Авторизация осуществляется через + сторонние сервисы (OAuth). + + + 4.2. Зарплатные данные используются исключительно в целях + формирования общедоступной статистики и отображаются в + обезличенном виде. + + + 4.3. Отзывы о компаниях публикуются без указания личных данных + автора. - 4.2. Вся предоставляемая Пользователем информация используется - исключительно в целях формирования общедоступной статистики. + 4.4. Заметки к интервью доступны только автору и тем + пользователям, которым автор предоставил доступ. - 4.3. Пользователь дает согласие на обработку предоставленных + 4.5. Пользователь дает согласие на обработку предоставленных данных с целью их анализа и формирования статистических отчетов. @@ -103,9 +145,9 @@ 6. Заключительные положения Настоящее Соглашение подлежит применению в соответствии с - законодательством Республики Казахстан Все возможные споры по поводу - Соглашения решаются путем переговоров, а при не достижении согласия — - в соответствии с действующим законодательством. + законодательством Республики Казахстан. Все возможные споры по поводу + Соглашения решаются путем переговоров, а при недостижении согласия — в + соответствии с действующим законодательством. @@ -115,12 +157,8 @@ В случае возникновения вопросов обращайтесь: По Email: ake111aa@ya.ru - По telegram: @maximgorbatyuk + По Telegram: @maximgorbatyuk - diff --git a/src/app/modules/home/components/privacy-policy-page/privacy-policy-page.component.ts b/src/app/modules/home/components/privacy-policy-page/privacy-policy-page.component.ts index c7e1f784..ff8657d2 100644 --- a/src/app/modules/home/components/privacy-policy-page/privacy-policy-page.component.ts +++ b/src/app/modules/home/components/privacy-policy-page/privacy-policy-page.component.ts @@ -10,7 +10,8 @@ export class PrivacyPolicyPageComponent implements OnDestroy { this.metaTagService.setPageMetaTags( "Пользовательское соглашение", "Пользовательское соглашение для пользователей сайта Techinterview.space.", - "/agreements/privacy-policy"); + "/agreements/privacy-policy", + ); } ngOnDestroy(): void { diff --git a/src/app/modules/home/components/telegram-bot/telegram-bot.component.scss b/src/app/modules/home/components/telegram-bot/telegram-bot.component.scss index cd9df37a..d1cd5f2f 100644 --- a/src/app/modules/home/components/telegram-bot/telegram-bot.component.scss +++ b/src/app/modules/home/components/telegram-bot/telegram-bot.component.scss @@ -1,3 +1,56 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// TELEGRAM BOT COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .clickable { cursor: pointer; + transition: color 0.2s ease; + + &:hover { + color: var(--warm-accent-primary); + } +} + +// Card styling +.card { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: 16px; + box-shadow: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color); +} + +// Telegram-specific styling +.telegram-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--warm-accent-primary); + font-weight: 600; + text-decoration: none; + transition: color 0.2s ease; + + &:hover { + color: var(--warm-accent-primary-hover); + text-decoration: underline; + } + + i { + font-size: 1.2rem; + } +} + +// Bot info box +.bot-info { + background-color: var(--warm-bg-accent); + border: 1px solid var(--warm-border-color); + border-radius: 12px; + padding: 1.5rem; + margin: 1rem 0; } diff --git a/src/app/modules/home/components/telegram-bot/telegram-bot.component.ts b/src/app/modules/home/components/telegram-bot/telegram-bot.component.ts index b645c3d4..8e4bf150 100644 --- a/src/app/modules/home/components/telegram-bot/telegram-bot.component.ts +++ b/src/app/modules/home/components/telegram-bot/telegram-bot.component.ts @@ -23,7 +23,8 @@ export class TelegramBotABoutComponent implements OnInit, OnDestroy { this.metaTagService.setPageMetaTags( "О ботах в Telegram", "О ботах в Telegram. Как использовать ботов для получения информации о зарплатах в IT и других областях в чате Telegram.", - "/about-telegram-bot"); + "/about-telegram-bot", + ); } ngOnInit(): void { diff --git a/src/app/modules/home/components/unsubscribe-me/unsubscribe-me.component.scss b/src/app/modules/home/components/unsubscribe-me/unsubscribe-me.component.scss index 93e00f46..0c7f8c0a 100644 --- a/src/app/modules/home/components/unsubscribe-me/unsubscribe-me.component.scss +++ b/src/app/modules/home/components/unsubscribe-me/unsubscribe-me.component.scss @@ -1,4 +1,43 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// UNSUBSCRIBE ME COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .subtitle { + font-family: "Nunito", sans-serif; font-size: 1.5rem; font-weight: 400; + color: var(--warm-text-secondary); +} + +// Card styling +.card { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: 16px; + box-shadow: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color); +} + +// Unsubscribe confirmation +.unsubscribe-message { + background-color: var(--warm-success-bg); + border: 1px solid var(--warm-success); + border-radius: 12px; + padding: 1.5rem; + color: var(--warm-text-primary); +} + +// Warning message +.unsubscribe-warning { + background-color: var(--warm-warning-bg); + border: 1px solid var(--warm-warning); + border-radius: 12px; + padding: 1.5rem; + color: var(--warm-text-primary); } diff --git a/src/app/modules/interviews/components/interview-edit-page/interview-edit-page.component.scss b/src/app/modules/interviews/components/interview-edit-page/interview-edit-page.component.scss index a2cb1b24..d54f10af 100644 --- a/src/app/modules/interviews/components/interview-edit-page/interview-edit-page.component.scss +++ b/src/app/modules/interviews/components/interview-edit-page/interview-edit-page.component.scss @@ -1,4 +1,139 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// INTERVIEW EDIT PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.interview-edit-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 2rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + .w-10 { width: 20%; max-width: 20%; } + +.edit-form-card { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); +} + +.form-section { + margin-bottom: 2rem; + + .section-title { + font-family: var(--warm-font-heading); + font-size: 1.125rem; + color: var(--warm-text-heading); + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--warm-border-color); + } +} + +.form-group { + margin-bottom: 1.5rem; + + label { + display: block; + color: var(--warm-text-secondary); + font-weight: 500; + margin-bottom: 0.5rem; + } + + input, + textarea, + select { + width: 100%; + background-color: var(--warm-bg-tertiary); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.75rem 1rem; + color: var(--warm-text-primary); + font-size: 1rem; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } + + textarea { + min-height: 120px; + resize: vertical; + } +} + +.question-item { + background-color: var(--warm-bg-tertiary); + border-radius: 10px; + padding: 1.25rem; + margin-bottom: 1rem; + border: 1px solid var(--warm-border-color); + + .question-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + + .question-number { + font-weight: 600; + color: var(--warm-accent-primary); + } + + .question-actions { + display: flex; + gap: 0.5rem; + } + } +} + +.add-question-btn { + width: 100%; + padding: 1rem; + border: 2px dashed var(--warm-border-color); + border-radius: 10px; + background-color: transparent; + color: var(--warm-text-muted); + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + border-color: var(--warm-accent-primary); + color: var(--warm-accent-primary); + background-color: var(--warm-bg-accent); + } +} + +.form-actions { + margin-top: 2rem; + display: flex; + gap: 1rem; + justify-content: flex-end; + padding-top: 1.5rem; + border-top: 1px solid var(--warm-border-color); +} diff --git a/src/app/modules/interviews/components/interview-edit-page/interview-template-select-item.ts b/src/app/modules/interviews/components/interview-edit-page/interview-template-select-item.ts index bffc0780..07ea2497 100644 --- a/src/app/modules/interviews/components/interview-edit-page/interview-template-select-item.ts +++ b/src/app/modules/interviews/components/interview-edit-page/interview-template-select-item.ts @@ -1,9 +1,7 @@ import { InterviewTemplate } from "@models/interview-models/interview-template"; import { SelectItem } from "@shared/select-boxes/select-item"; -export class InterviewTemplateSelectItem - implements SelectItem -{ +export class InterviewTemplateSelectItem implements SelectItem { readonly value: string; readonly label: string; readonly item: InterviewTemplate; diff --git a/src/app/modules/interviews/components/interview-page/interview-page.component.scss b/src/app/modules/interviews/components/interview-page/interview-page.component.scss index e69de29b..940c80c4 100644 --- a/src/app/modules/interviews/components/interview-page/interview-page.component.scss +++ b/src/app/modules/interviews/components/interview-page/interview-page.component.scss @@ -0,0 +1,150 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// INTERVIEW PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.interview-page { + padding: 1.5rem 0; +} + +.interview-header { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + margin-bottom: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + + .interview-title { + font-family: var(--warm-font-heading); + font-size: 1.75rem; + font-weight: 700; + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .interview-meta { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + color: var(--warm-text-muted); + font-size: 0.875rem; + + .meta-item { + display: flex; + align-items: center; + gap: 0.5rem; + + i { + color: var(--warm-accent-secondary); + } + } + } +} + +.interview-content { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1.5rem; + box-shadow: var(--warm-shadow-sm); + border: 1px solid var(--warm-border-color); +} + +.interview-section { + margin-bottom: 2rem; + + &:last-child { + margin-bottom: 0; + } + + .section-title { + font-family: var(--warm-font-heading); + font-size: 1.25rem; + font-weight: 600; + color: var(--warm-text-heading); + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--warm-bg-accent); + } + + .section-content { + color: var(--warm-text-primary); + line-height: 1.7; + } +} + +.question-block { + background-color: var(--warm-bg-tertiary); + border-radius: 10px; + padding: 1.25rem; + margin-bottom: 1rem; + border-left: 4px solid var(--warm-accent-primary); + + .question-text { + font-weight: 600; + color: var(--warm-text-heading); + margin-bottom: 0.75rem; + } + + .question-answer { + color: var(--warm-text-secondary); + line-height: 1.6; + } + + .question-grade { + display: inline-flex; + align-items: center; + gap: 0.375rem; + margin-top: 0.75rem; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + background-color: var(--warm-bg-accent); + color: var(--warm-accent-primary); + } +} + +.interview-actions { + display: flex; + gap: 1rem; + margin-top: 2rem; +} + +.interview-summary { + background-color: var(--warm-bg-accent); + border-radius: 12px; + padding: 1.5rem; + margin-top: 2rem; + + h4 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 1rem; + } + + .summary-item { + display: flex; + justify-content: space-between; + padding: 0.5rem 0; + border-bottom: 1px solid var(--warm-border-color); + + &:last-child { + border-bottom: none; + } + + .label { + color: var(--warm-text-secondary); + } + + .value { + font-weight: 600; + color: var(--warm-text-heading); + } + } +} diff --git a/src/app/modules/interviews/components/interview-template-edit-page/interview-template-edit-page.component.scss b/src/app/modules/interviews/components/interview-template-edit-page/interview-template-edit-page.component.scss index e69de29b..51a54a87 100644 --- a/src/app/modules/interviews/components/interview-template-edit-page/interview-template-edit-page.component.scss +++ b/src/app/modules/interviews/components/interview-template-edit-page/interview-template-edit-page.component.scss @@ -0,0 +1,173 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// INTERVIEW TEMPLATE EDIT PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.template-edit-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 2rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.edit-form-card { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); +} + +.form-section { + margin-bottom: 2rem; + + .section-title { + font-family: var(--warm-font-heading); + font-size: 1.125rem; + color: var(--warm-text-heading); + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--warm-border-color); + } +} + +.form-group { + margin-bottom: 1.5rem; + + label { + display: block; + color: var(--warm-text-secondary); + font-weight: 500; + margin-bottom: 0.5rem; + } + + input, + textarea, + select { + width: 100%; + background-color: var(--warm-bg-tertiary); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.75rem 1rem; + color: var(--warm-text-primary); + font-size: 1rem; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } + + textarea { + min-height: 100px; + resize: vertical; + } +} + +.questions-editor { + .question-item { + background-color: var(--warm-bg-tertiary); + border-radius: 10px; + padding: 1.25rem; + margin-bottom: 1rem; + border: 1px solid var(--warm-border-color); + position: relative; + + .drag-handle { + position: absolute; + left: 0.5rem; + top: 50%; + transform: translateY(-50%); + color: var(--warm-text-muted); + cursor: grab; + + &:active { + cursor: grabbing; + } + } + + .question-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + padding-left: 1.5rem; + + .question-number { + font-weight: 600; + color: var(--warm-accent-primary); + } + + .question-actions { + display: flex; + gap: 0.5rem; + } + } + + .question-content { + padding-left: 1.5rem; + } + } +} + +.add-question-btn { + width: 100%; + padding: 1rem; + border: 2px dashed var(--warm-border-color); + border-radius: 10px; + background-color: transparent; + color: var(--warm-text-muted); + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + border-color: var(--warm-accent-primary); + color: var(--warm-accent-primary); + background-color: var(--warm-bg-accent); + } +} + +.visibility-toggle { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + background-color: var(--warm-bg-tertiary); + border-radius: 8px; + + .toggle-label { + color: var(--warm-text-secondary); + } + + .toggle-description { + color: var(--warm-text-muted); + font-size: 0.875rem; + } +} + +.form-actions { + margin-top: 2rem; + display: flex; + gap: 1rem; + justify-content: flex-end; + padding-top: 1.5rem; + border-top: 1px solid var(--warm-border-color); +} diff --git a/src/app/modules/interviews/components/interview-template-page/interview-template-page.component.scss b/src/app/modules/interviews/components/interview-template-page/interview-template-page.component.scss index e69de29b..bb9716fb 100644 --- a/src/app/modules/interviews/components/interview-template-page/interview-template-page.component.scss +++ b/src/app/modules/interviews/components/interview-template-page/interview-template-page.component.scss @@ -0,0 +1,148 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// INTERVIEW TEMPLATE PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.template-page { + padding: 1.5rem 0; +} + +.template-header { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + margin-bottom: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + + .template-title { + font-family: var(--warm-font-heading); + font-size: 1.75rem; + font-weight: 700; + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .template-description { + color: var(--warm-text-secondary); + line-height: 1.6; + margin-bottom: 1rem; + } + + .template-meta { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + color: var(--warm-text-muted); + font-size: 0.875rem; + + .meta-item { + display: flex; + align-items: center; + gap: 0.5rem; + + i { + color: var(--warm-accent-secondary); + } + } + } +} + +.template-content { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.5rem; + box-shadow: var(--warm-shadow-sm); + border: 1px solid var(--warm-border-color); +} + +.questions-list { + .question-item { + background-color: var(--warm-bg-tertiary); + border-radius: 10px; + padding: 1.25rem; + margin-bottom: 1rem; + border-left: 4px solid var(--warm-accent-primary); + + &:last-child { + margin-bottom: 0; + } + + .question-number { + font-size: 0.75rem; + font-weight: 600; + color: var(--warm-accent-primary); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.5rem; + } + + .question-text { + font-weight: 500; + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .question-hint { + color: var(--warm-text-muted); + font-size: 0.875rem; + font-style: italic; + } + } +} + +.template-actions { + display: flex; + gap: 1rem; + margin-top: 2rem; +} + +.template-stats { + background-color: var(--warm-bg-accent); + border-radius: 12px; + padding: 1.5rem; + margin-top: 2rem; + + .stat-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 0; + border-bottom: 1px solid var(--warm-border-color); + + &:last-child { + border-bottom: none; + } + + .stat-icon { + width: 40px; + height: 40px; + border-radius: 8px; + background-color: var(--warm-card-bg); + display: flex; + align-items: center; + justify-content: center; + + i { + color: var(--warm-accent-primary); + } + } + + .stat-content { + .stat-value { + font-weight: 700; + color: var(--warm-text-heading); + font-size: 1.25rem; + } + + .stat-label { + color: var(--warm-text-muted); + font-size: 0.8125rem; + } + } + } +} diff --git a/src/app/modules/interviews/components/interviews-home/interviews-home.component.scss b/src/app/modules/interviews/components/interviews-home/interviews-home.component.scss index fab889f8..0115e270 100644 --- a/src/app/modules/interviews/components/interviews-home/interviews-home.component.scss +++ b/src/app/modules/interviews/components/interviews-home/interviews-home.component.scss @@ -1,9 +1,16 @@ -.lead { - font-size: 2.2rem; +// ═══════════════════════════════════════════════════════════════════════════════ +// INTERVIEWS HOME COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; } -.display-1 { - font-weight: 400; +.lead { + font-size: 2.2rem; + color: var(--warm-text-heading); + font-family: var(--warm-font-heading); } .feature-title { @@ -11,6 +18,8 @@ font-size: 2.1rem; font-weight: 600; line-height: 1.2; + color: var(--warm-text-heading); + font-family: var(--warm-font-heading); } .feature { @@ -22,15 +31,114 @@ max-width: 100%; height: auto; border-radius: 15px; - border: rgb(216, 216, 216) 1px solid; - box-shadow: 0 0.5rem 1rem #00000026; + border: 1px solid var(--warm-border-color); + box-shadow: var(--warm-shadow-lg); + transition: + transform 0.3s ease, + box-shadow 0.3s ease; + + &:hover { + transform: translateY(-4px); + box-shadow: var(--warm-shadow-xl); + } } .feature-subtitle { font-size: 1.2rem; - color: #6c757d; + color: var(--warm-text-secondary); + line-height: 1.6; } .bg-white { - background-color: #fff; + background-color: var(--warm-card-bg); +} + +// Hero section +.hero-section { + padding: 4rem 0; + background: linear-gradient( + 135deg, + var(--warm-bg-primary) 0%, + var(--warm-bg-accent) 100% + ); + + .hero-title { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + font-weight: 700; + } + + .hero-description { + color: var(--warm-text-secondary); + font-size: 1.25rem; + line-height: 1.6; + } +} + +// Feature cards +.feature-card { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + transition: + transform 0.3s ease, + box-shadow 0.3s ease; + height: 100%; + + &:hover { + transform: translateY(-4px); + box-shadow: var(--warm-shadow-lg); + } + + .feature-icon { + width: 60px; + height: 60px; + border-radius: 12px; + background-color: var(--warm-bg-accent); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1.5rem; + + i { + font-size: 1.75rem; + color: var(--warm-accent-primary); + } + } + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 0.75rem; + } + + p { + color: var(--warm-text-secondary); + line-height: 1.6; + } +} + +// CTA section +.cta-section { + background-color: var(--warm-bg-accent); + border-radius: 16px; + padding: 3rem; + text-align: center; + margin: 3rem 0; + + h2 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 1rem; + } + + p { + color: var(--warm-text-secondary); + margin-bottom: 1.5rem; + max-width: 600px; + margin-left: auto; + margin-right: auto; + } } diff --git a/src/app/modules/interviews/components/my-interview-templates/my-interview-templates.component.scss b/src/app/modules/interviews/components/my-interview-templates/my-interview-templates.component.scss index e69de29b..08990077 100644 --- a/src/app/modules/interviews/components/my-interview-templates/my-interview-templates.component.scss +++ b/src/app/modules/interviews/components/my-interview-templates/my-interview-templates.component.scss @@ -0,0 +1,131 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// MY INTERVIEW TEMPLATES COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.my-templates-page { + padding: 1.5rem 0; +} + +.page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.templates-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.template-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.5rem; + border: 1px solid var(--warm-border-color); + box-shadow: var(--warm-shadow-sm); + transition: all 0.2s ease; + cursor: pointer; + + &:hover { + transform: translateY(-4px); + box-shadow: var(--warm-shadow-lg); + border-color: var(--warm-accent-primary); + } + + .template-title { + font-family: var(--warm-font-heading); + font-weight: 600; + color: var(--warm-text-heading); + font-size: 1.125rem; + margin-bottom: 0.5rem; + } + + .template-description { + color: var(--warm-text-secondary); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .template-meta { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 1rem; + border-top: 1px solid var(--warm-border-color); + + .questions-count { + color: var(--warm-text-muted); + font-size: 0.8125rem; + + i { + margin-right: 0.375rem; + color: var(--warm-accent-secondary); + } + } + + .visibility-badge { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.2rem 0.625rem; + border-radius: 20px; + font-size: 0.6875rem; + font-weight: 600; + text-transform: uppercase; + + &.public { + background-color: rgba(129, 178, 154, 0.15); + color: var(--warm-success); + } + + &.private { + background-color: rgba(122, 122, 122, 0.15); + color: var(--warm-text-muted); + } + } + } +} + +.empty-state { + text-align: center; + padding: 4rem 2rem; + background-color: var(--warm-card-bg); + border-radius: 12px; + border: 1px dashed var(--warm-border-color); + + i { + font-size: 4rem; + color: var(--warm-text-muted); + opacity: 0.5; + margin-bottom: 1rem; + } + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + } + + p { + color: var(--warm-text-muted); + max-width: 400px; + margin: 0 auto 1.5rem; + } +} diff --git a/src/app/modules/interviews/components/my-interviews/my-interviews.component.scss b/src/app/modules/interviews/components/my-interviews/my-interviews.component.scss index e69de29b..5a9a145c 100644 --- a/src/app/modules/interviews/components/my-interviews/my-interviews.component.scss +++ b/src/app/modules/interviews/components/my-interviews/my-interviews.component.scss @@ -0,0 +1,142 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// MY INTERVIEWS COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.my-interviews-page { + padding: 1.5rem 0; +} + +.page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + } +} + +.interviews-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.interview-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.25rem 1.5rem; + border: 1px solid var(--warm-border-color); + box-shadow: var(--warm-shadow-sm); + transition: all 0.2s ease; + cursor: pointer; + + &:hover { + transform: translateY(-2px); + box-shadow: var(--warm-shadow-md); + border-color: var(--warm-accent-primary); + } + + .interview-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.75rem; + + .interview-title { + font-family: var(--warm-font-heading); + font-weight: 600; + color: var(--warm-text-heading); + font-size: 1.125rem; + } + + .interview-date { + color: var(--warm-text-muted); + font-size: 0.8125rem; + } + } + + .interview-meta { + display: flex; + gap: 1rem; + color: var(--warm-text-secondary); + font-size: 0.875rem; + + .meta-item { + display: flex; + align-items: center; + gap: 0.375rem; + + i { + color: var(--warm-accent-secondary); + font-size: 0.875rem; + } + } + } + + .interview-status { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + margin-top: 0.75rem; + + &.status-draft { + background-color: rgba(122, 122, 122, 0.15); + color: var(--warm-text-muted); + } + + &.status-completed { + background-color: rgba(129, 178, 154, 0.15); + color: var(--warm-success); + } + + &.status-in-progress { + background-color: rgba(224, 122, 95, 0.15); + color: var(--warm-accent-primary); + } + } +} + +.empty-state { + text-align: center; + padding: 4rem 2rem; + background-color: var(--warm-card-bg); + border-radius: 12px; + border: 1px dashed var(--warm-border-color); + + i { + font-size: 4rem; + color: var(--warm-text-muted); + opacity: 0.5; + margin-bottom: 1rem; + } + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + } + + p { + color: var(--warm-text-muted); + max-width: 400px; + margin: 0 auto 1.5rem; + } +} + +.pagination-wrapper { + margin-top: 2rem; + display: flex; + justify-content: center; +} diff --git a/src/app/modules/interviews/components/public-interview-templates/public-interview-templates.component.scss b/src/app/modules/interviews/components/public-interview-templates/public-interview-templates.component.scss index e69de29b..0cc5130d 100644 --- a/src/app/modules/interviews/components/public-interview-templates/public-interview-templates.component.scss +++ b/src/app/modules/interviews/components/public-interview-templates/public-interview-templates.component.scss @@ -0,0 +1,194 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// PUBLIC INTERVIEW TEMPLATES COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.public-templates-page { + padding: 1.5rem 0; +} + +.page-header { + margin-bottom: 2rem; + + h1 { + font-family: var(--warm-font-heading); + color: var(--warm-text-heading); + margin-bottom: 0.5rem; + } + + .page-description { + color: var(--warm-text-secondary); + } +} + +.search-section { + margin-bottom: 2rem; + + .search-input { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-border-color); + border-radius: 10px; + padding: 0.875rem 1.25rem; + font-size: 1rem; + color: var(--warm-text-primary); + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &:focus { + border-color: var(--warm-accent-primary); + box-shadow: 0 0 0 3px rgba(224, 122, 95, 0.15); + outline: none; + } + + &::placeholder { + color: var(--warm-text-muted); + } + } +} + +.templates-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 1.5rem; +} + +.template-card { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.5rem; + border: 1px solid var(--warm-border-color); + box-shadow: var(--warm-shadow-sm); + transition: all 0.2s ease; + cursor: pointer; + + &:hover { + transform: translateY(-4px); + box-shadow: var(--warm-shadow-lg); + border-color: var(--warm-accent-primary); + } + + .template-header { + margin-bottom: 1rem; + + .template-title { + font-family: var(--warm-font-heading); + font-weight: 600; + color: var(--warm-text-heading); + font-size: 1.125rem; + margin-bottom: 0.375rem; + } + + .template-author { + color: var(--warm-text-muted); + font-size: 0.8125rem; + + a { + color: var(--warm-accent-primary); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + } + + .template-description { + color: var(--warm-text-secondary); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .template-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 1rem; + border-top: 1px solid var(--warm-border-color); + + .template-stats { + display: flex; + gap: 1rem; + color: var(--warm-text-muted); + font-size: 0.8125rem; + + .stat-item { + display: flex; + align-items: center; + gap: 0.375rem; + + i { + color: var(--warm-accent-secondary); + } + } + } + + .use-btn { + padding: 0.375rem 0.875rem; + font-size: 0.8125rem; + } + } +} + +.category-filter { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 1.5rem; + + .category-btn { + padding: 0.5rem 1rem; + border-radius: 20px; + border: 1px solid var(--warm-border-color); + background-color: var(--warm-card-bg); + color: var(--warm-text-secondary); + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + border-color: var(--warm-accent-primary); + color: var(--warm-accent-primary); + } + + &.active { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + } + } +} + +.empty-state { + text-align: center; + padding: 4rem 2rem; + color: var(--warm-text-muted); + + i { + font-size: 4rem; + margin-bottom: 1rem; + opacity: 0.5; + } + + h3 { + font-family: var(--warm-font-heading); + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + } +} + +.pagination-wrapper { + margin-top: 2rem; + display: flex; + justify-content: center; +} diff --git a/src/app/modules/interviews/components/text-with-linebreaks/text-with-linebreaks.component.scss b/src/app/modules/interviews/components/text-with-linebreaks/text-with-linebreaks.component.scss index e69de29b..4c30ffe2 100644 --- a/src/app/modules/interviews/components/text-with-linebreaks/text-with-linebreaks.component.scss +++ b/src/app/modules/interviews/components/text-with-linebreaks/text-with-linebreaks.component.scss @@ -0,0 +1,23 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// TEXT WITH LINEBREAKS COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.text-content { + color: var(--warm-text-primary); + line-height: 1.7; + white-space: pre-wrap; + word-wrap: break-word; + + p { + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } + } +} diff --git a/src/app/modules/salaries/components/add-salary/add-salary.component.scss b/src/app/modules/salaries/components/add-salary/add-salary.component.scss index 43e99b8b..d77e408b 100644 --- a/src/app/modules/salaries/components/add-salary/add-salary.component.scss +++ b/src/app/modules/salaries/components/add-salary/add-salary.component.scss @@ -1,6 +1,25 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// ADD SALARY COMPONENT STYLES +// Warm & Friendly Theme - Salary Survey Form +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +// ───────────────────────────────────────────────────────────────────────────────── +// NG-SELECT OVERRIDES +// ───────────────────────────────────────────────────────────────────────────────── + .ng-select .ng-select-container { min-height: 46px; height: 46px; + background-color: var(--warm-input-bg); + border-color: var(--warm-input-border); + border-radius: 10px; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; } .ng-select.ng-select-single .ng-select-container { @@ -8,10 +27,89 @@ height: 46px; } -.input-container { - margin-top: 2rem; +.ng-select.ng-select-focused .ng-select-container { + border-color: var(--warm-input-focus-border); + box-shadow: 0 0 0 3px var(--warm-input-focus-ring); +} + +.ng-select .ng-dropdown-panel { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-border-color); + border-radius: 10px; + box-shadow: + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color); } +.ng-select .ng-option { + color: var(--warm-text-primary); + transition: background-color 0.15s ease; + + &:hover, + &.ng-option-marked { + background-color: var(--warm-bg-accent); + } + + &.ng-option-selected { + background-color: var(--warm-accent-primary); + color: white; + } +} + +// ───────────────────────────────────────────────────────────────────────────────── +// FORM CONTAINER +// ───────────────────────────────────────────────────────────────────────────────── + .form-container { - padding: 3rem 10rem; + padding: 2rem 3rem; + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: 16px; + box-shadow: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color); + + @media (min-width: 992px) { + padding: 3rem 6rem; + } + + @media (max-width: 768px) { + padding: 1.5rem; + border-radius: 12px; + } +} + +// ───────────────────────────────────────────────────────────────────────────────── +// INPUT CONTAINERS +// ───────────────────────────────────────────────────────────────────────────────── + +.input-container { + margin-top: 1.5rem; +} + +.input-label { + font-weight: 600; + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; + font-size: 0.95rem; +} + +.input-hint { + font-size: 0.85rem; + color: var(--warm-text-muted); + margin-top: 0.25rem; +} + +// ───────────────────────────────────────────────────────────────────────────────── +// RESPONSIVE +// ───────────────────────────────────────────────────────────────────────────────── + +@media (max-width: 768px) { + .form-container { + padding: 1.5rem 1rem; + } + + .input-container { + margin-top: 1.25rem; + } } diff --git a/src/app/modules/salaries/components/cities-doughnut-chart/cities-doughnut-chart.component.scss b/src/app/modules/salaries/components/cities-doughnut-chart/cities-doughnut-chart.component.scss index bdb33c29..97be133f 100644 --- a/src/app/modules/salaries/components/cities-doughnut-chart/cities-doughnut-chart.component.scss +++ b/src/app/modules/salaries/components/cities-doughnut-chart/cities-doughnut-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// CITIES DOUGHNUT CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; } @@ -8,12 +17,19 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } .form-check-label { cursor: pointer; + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.html b/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.html index 8bdf0590..75f716a6 100644 --- a/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.html +++ b/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.html @@ -15,7 +15,10 @@ Динамика курсов валют (click)="refresh()" [disabled]="isLoading" > - + Обновить @@ -32,7 +35,9 @@ Динамика курсов валют diff --git a/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.scss b/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.scss index a79dd82e..d0a03ec1 100644 --- a/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.scss +++ b/src/app/modules/salaries/components/currencies-chart-page/currencies-chart-page.component.scss @@ -1,10 +1,56 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// CURRENCIES CHART PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .chart-container { position: relative; height: 450px; width: 100%; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } canvas { width: 100% !important; height: 100% !important; } + +.currency-card { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: 16px; + padding: 1.5rem; + box-shadow: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color); +} + +.currency-title { + font-family: "Lora", Georgia, serif; + font-weight: 600; + color: var(--warm-text-heading); +} + +.currency-value { + font-size: 1.5rem; + font-weight: 700; + color: var(--warm-accent-primary); +} + +.currency-change { + font-size: 0.875rem; + + &.positive { + color: var(--warm-success); + } + + &.negative { + color: var(--warm-danger); + } +} diff --git a/src/app/modules/salaries/components/edit-salary/edit-salary.component.scss b/src/app/modules/salaries/components/edit-salary/edit-salary.component.scss index 8e7c9d32..09bfd04f 100644 --- a/src/app/modules/salaries/components/edit-salary/edit-salary.component.scss +++ b/src/app/modules/salaries/components/edit-salary/edit-salary.component.scss @@ -1,3 +1,33 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// EDIT SALARY COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .salary-value-wrapper { cursor: pointer; + padding: 0.5rem 1rem; + border-radius: 8px; + background-color: var(--warm-bg-accent); + border: 1px solid var(--warm-border-color); + transition: all 0.2s ease; + + &:hover { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + } +} + +.edit-form { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: 12px; + padding: 1.5rem; + box-shadow: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color); } diff --git a/src/app/modules/salaries/components/grades-min-max-salaries-chart/grades-min-max-chart.component.scss b/src/app/modules/salaries/components/grades-min-max-salaries-chart/grades-min-max-chart.component.scss index 9a36cd43..1f07b500 100644 --- a/src/app/modules/salaries/components/grades-min-max-salaries-chart/grades-min-max-chart.component.scss +++ b/src/app/modules/salaries/components/grades-min-max-salaries-chart/grades-min-max-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// GRADES MIN MAX CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 350px; } @@ -8,8 +17,14 @@ canvas { height: 100%; min-height: 350px; max-height: 550px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.html b/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.html index 6c5085b5..c1ae2d6c 100644 --- a/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.html +++ b/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.html @@ -3,14 +3,18 @@ - Период: {{ data.from | date:'dd.MM.yyyy' }} - {{ data.to | date:'dd.MM.yyyy' }} + Период: {{ data.from | date: "dd.MM.yyyy" }} - + {{ data.to | date: "dd.MM.yyyy" }} 0; else noData"> - + - {{ template.name || 'Общие данные' }} + {{ template.name || "Общие данные" }} diff --git a/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.scss b/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.scss index 0a1c9177..4e0bfa4e 100644 --- a/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.scss +++ b/src/app/modules/salaries/components/historical-charts-page/historical-charts-page.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// HISTORICAL CHARTS PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + #canvas-historical-chart { min-height: 400px; } @@ -7,4 +16,25 @@ width: 100%; height: 100%; min-height: 400px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; +} + +.historical-section { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: 16px; + padding: 1.5rem; + margin-bottom: 2rem; + box-shadow: + 0 2px 4px var(--warm-shadow-color), + 0 4px 8px var(--warm-shadow-color); +} + +.historical-title { + font-family: "Lora", Georgia, serif; + font-weight: 600; + color: var(--warm-text-heading); + margin-bottom: 1rem; } diff --git a/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-charts-page.component.ts b/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-charts-page.component.ts index 269e7375..25d0e9d3 100644 --- a/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-charts-page.component.ts +++ b/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-charts-page.component.ts @@ -8,7 +8,9 @@ import { HistoricalSalariesChartObject } from "./historical-salaries-chart-objec styleUrls: ["./historical-salaries-chart.component.scss"], standalone: false, }) -export class HistoricalSalariesChartComponent implements AfterViewInit, OnDestroy { +export class HistoricalSalariesChartComponent + implements AfterViewInit, OnDestroy +{ @Input() template: HistoricalDataByTemplate | null = null; @@ -28,7 +30,10 @@ export class HistoricalSalariesChartComponent implements AfterViewInit, OnDestro return; } - this.chart = new HistoricalSalariesChartObject(this.canvasId, this.template); + this.chart = new HistoricalSalariesChartObject( + this.canvasId, + this.template, + ); const chartEl = document.getElementById(this.canvasId); if (chartEl != null && chartEl.parentElement != null) { diff --git a/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-salaries-chart.component.scss b/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-salaries-chart.component.scss index 5932c76c..0ec81d49 100644 --- a/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-salaries-chart.component.scss +++ b/src/app/modules/salaries/components/historical-charts-page/historical-salaries-chart/historical-salaries-chart.component.scss @@ -1,8 +1,20 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// HISTORICAL SALARIES CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .canvas-container { position: relative; width: 100%; height: 100%; min-height: 600px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; canvas { min-height: 600px; diff --git a/src/app/modules/salaries/components/historical-charts-page/survey-historical-chart/survey-historical-chart.component.scss b/src/app/modules/salaries/components/historical-charts-page/survey-historical-chart/survey-historical-chart.component.scss index 4f479315..e3889b9e 100644 --- a/src/app/modules/salaries/components/historical-charts-page/survey-historical-chart/survey-historical-chart.component.scss +++ b/src/app/modules/salaries/components/historical-charts-page/survey-historical-chart/survey-historical-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SURVEY HISTORICAL CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 400px; } @@ -7,4 +16,7 @@ canvas { width: 100%; height: 100%; min-height: 400px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } diff --git a/src/app/modules/salaries/components/people-by-age-chart/people-by-age-chart.component.scss b/src/app/modules/salaries/components/people-by-age-chart/people-by-age-chart.component.scss index 2044c5e8..2ebe2f1f 100644 --- a/src/app/modules/salaries/components/people-by-age-chart/people-by-age-chart.component.scss +++ b/src/app/modules/salaries/components/people-by-age-chart/people-by-age-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// PEOPLE BY AGE CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; @@ -12,6 +21,9 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; @media (max-width: 768px) { min-height: 200px; @@ -20,9 +32,13 @@ canvas { } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } .form-check-label { cursor: pointer; + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/people-by-experience-chart/people-by-experience-chart.component.scss b/src/app/modules/salaries/components/people-by-experience-chart/people-by-experience-chart.component.scss index 2044c5e8..19b74483 100644 --- a/src/app/modules/salaries/components/people-by-experience-chart/people-by-experience-chart.component.scss +++ b/src/app/modules/salaries/components/people-by-experience-chart/people-by-experience-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// PEOPLE BY EXPERIENCE CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; @@ -12,6 +21,9 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; @media (max-width: 768px) { min-height: 200px; @@ -20,9 +32,13 @@ canvas { } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } .form-check-label { cursor: pointer; + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/people-by-gender-chart/people-by-gender-chart.component.scss b/src/app/modules/salaries/components/people-by-gender-chart/people-by-gender-chart.component.scss index 2c7e5c12..02c58e4d 100644 --- a/src/app/modules/salaries/components/people-by-gender-chart/people-by-gender-chart.component.scss +++ b/src/app/modules/salaries/components/people-by-gender-chart/people-by-gender-chart.component.scss @@ -1,8 +1,26 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// PEOPLE BY GENDER CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .progress-bar { min-height: 30px; + background-color: var(--warm-accent-primary); + transition: width 0.3s ease; } .progress { min-height: 30px; cursor: pointer; + background-color: var(--warm-bg-tertiary); + border-radius: 8px; + overflow: hidden; + + &:hover { + box-shadow: 0 2px 8px var(--warm-shadow-color); + } } diff --git a/src/app/modules/salaries/components/people-by-gender-chart/salaries-by-gender-chart/salaries-by-gender-chart.component.scss b/src/app/modules/salaries/components/people-by-gender-chart/salaries-by-gender-chart/salaries-by-gender-chart.component.scss index 9e7bc7a2..c0754ba4 100644 --- a/src/app/modules/salaries/components/people-by-gender-chart/salaries-by-gender-chart/salaries-by-gender-chart.component.scss +++ b/src/app/modules/salaries/components/people-by-gender-chart/salaries-by-gender-chart/salaries-by-gender-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES BY GENDER CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; @@ -12,6 +21,9 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; @media (max-width: 768px) { min-height: 200px; @@ -20,5 +32,8 @@ canvas { } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/people-by-grades-chart/people-by-grades-chart.component.scss b/src/app/modules/salaries/components/people-by-grades-chart/people-by-grades-chart.component.scss index 2c7e5c12..d6439b81 100644 --- a/src/app/modules/salaries/components/people-by-grades-chart/people-by-grades-chart.component.scss +++ b/src/app/modules/salaries/components/people-by-grades-chart/people-by-grades-chart.component.scss @@ -1,8 +1,26 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// PEOPLE BY GRADES CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .progress-bar { min-height: 30px; + background-color: var(--warm-accent-primary); + transition: width 0.3s ease; } .progress { min-height: 30px; cursor: pointer; + background-color: var(--warm-bg-tertiary); + border-radius: 8px; + overflow: hidden; + + &:hover { + box-shadow: 0 2px 8px var(--warm-shadow-color); + } } diff --git a/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss index 0563ea33..7bfacf99 100644 --- a/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss +++ b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// PROFESSIONS DISTRIBUTION CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; } @@ -8,8 +17,14 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/salaries-by-age-chart/salaries-by-age-chart.component.scss b/src/app/modules/salaries/components/salaries-by-age-chart/salaries-by-age-chart.component.scss index 0563ea33..f034817c 100644 --- a/src/app/modules/salaries/components/salaries-by-age-chart/salaries-by-age-chart.component.scss +++ b/src/app/modules/salaries/components/salaries-by-age-chart/salaries-by-age-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES BY AGE CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; } @@ -8,8 +17,14 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/salaries-by-cities-chart/salaries-by-city-chart.component.scss b/src/app/modules/salaries/components/salaries-by-cities-chart/salaries-by-city-chart.component.scss index 9e7bc7a2..3f4c0b57 100644 --- a/src/app/modules/salaries/components/salaries-by-cities-chart/salaries-by-city-chart.component.scss +++ b/src/app/modules/salaries/components/salaries-by-cities-chart/salaries-by-city-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES BY CITIES CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; @@ -12,6 +21,9 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; @media (max-width: 768px) { min-height: 200px; @@ -20,5 +32,8 @@ canvas { } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/salaries-by-experience-chart/salaries-by-experience-chart.component.scss b/src/app/modules/salaries/components/salaries-by-experience-chart/salaries-by-experience-chart.component.scss index 0563ea33..dad4f202 100644 --- a/src/app/modules/salaries/components/salaries-by-experience-chart/salaries-by-experience-chart.component.scss +++ b/src/app/modules/salaries/components/salaries-by-experience-chart/salaries-by-experience-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES BY EXPERIENCE CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + canvas { min-height: 250px; } @@ -8,8 +17,14 @@ canvas { height: 100%; min-height: 250px; max-height: 500px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss index d199abae..c0472229 100644 --- a/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss +++ b/src/app/modules/salaries/components/salaries-by-grades-chart/salaries-by-grades-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES BY GRADES CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + #canvas { min-height: 400px; } @@ -7,8 +16,25 @@ width: 100%; height: 100%; min-height: 400px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); + transition: background-color 0.2s ease; + + &:hover { + background-color: var(--warm-bg-accent); + } + + &.active { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + } } diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-adding-chart/salaries-adding-chart.component.scss b/src/app/modules/salaries/components/salaries-chart/salaries-adding-chart/salaries-adding-chart.component.scss index 56eade44..75711cb1 100644 --- a/src/app/modules/salaries/components/salaries-chart/salaries-adding-chart/salaries-adding-chart.component.scss +++ b/src/app/modules/salaries/components/salaries-chart/salaries-adding-chart/salaries-adding-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES ADDING CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + #canvas-adding-trend-chart { min-height: 400px; } @@ -7,4 +16,7 @@ width: 100%; height: 100%; min-height: 400px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html index 2b183c68..fcc93d63 100644 --- a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html +++ b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html @@ -522,20 +522,6 @@ Обновим ваши данные? - - - - Загружаем статистику - - - - - - + diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.scss b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.scss index 6cb3e7a2..2c780953 100644 --- a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.scss +++ b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.scss @@ -1,14 +1,47 @@ -.blur { - -webkit-filter: blur(6px); /* Chrome, Safari, Opera */ - filter: blur(6px); -} +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES CHART COMPONENT STYLES +// Warm & Friendly Theme +// Component-specific styles only (global styles in styles.scss) +// ═══════════════════════════════════════════════════════════════════════════════ -.chapter-title { - margin-top: 5rem; +:host { + display: block; } +// ───────────────────────────────────────────────────────────────────────────────── +// NON-AUTHORIZED BLOCK +// ───────────────────────────────────────────────────────────────────────────────── + .non-authorized-block { .alert { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-accent-primary); + border-left: 4px solid var(--warm-accent-primary); + border-radius: 12px; + box-shadow: + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color); + color: var(--warm-text-primary); + padding: 1.5rem; + } +} + +// ───────────────────────────────────────────────────────────────────────────────── +// RESPONSIVE +// ───────────────────────────────────────────────────────────────────────────────── + +@media (max-width: 768px) { + .chapter-title { + margin-top: 3rem; + font-size: 1.5rem; + } + + .chart-card { + padding: 1rem; + border-radius: 12px; + } + + .stats-value { + font-size: 1.5rem; } } diff --git a/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html b/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html index 258be510..8e0f1edb 100644 --- a/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html +++ b/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.html @@ -40,7 +40,7 @@ diff --git a/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.scss b/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.scss index e69de29b..d61c41b2 100644 --- a/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.scss +++ b/src/app/modules/salaries/components/salaries-chart/salary-block-value/salary-block-value.component.scss @@ -0,0 +1,7 @@ +.cat-image { + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.1); + } +} diff --git a/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.html b/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.html index 46e8d4c5..3229658f 100644 --- a/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.html +++ b/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.html @@ -81,10 +81,12 @@ - Применить + + Применить + Сброс diff --git a/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.scss b/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.scss index 9d8a289e..7f6088e7 100644 --- a/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.scss +++ b/src/app/modules/salaries/components/salaries-chart/salary-chart-global-filters/salary-chart-global-filters.component.scss @@ -1,3 +1,60 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARY CHART GLOBAL FILTERS COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + .text-sm { font-size: 0.875rem; } + +.filter-container { + background-color: var(--warm-bg-secondary); + border: 1px solid var(--warm-border-color); + border-radius: 12px; + padding: 1rem 1.25rem; + margin-bottom: 1.5rem; +} + +.filter-label { + font-weight: 600; + font-size: 0.875rem; + color: var(--warm-text-secondary); + margin-bottom: 0.5rem; +} + +.filter-btn { + background-color: var(--warm-bg-elevated); + border: 1px solid var(--warm-border-color); + border-radius: 8px; + padding: 0.5rem 1rem; + color: var(--warm-text-primary); + font-size: 0.875rem; + transition: all 0.2s ease; + + &:hover { + background-color: var(--warm-bg-accent); + border-color: var(--warm-accent-primary); + } + + &.active { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + } +} + +.filter-reset { + color: var(--warm-accent-primary); + font-size: 0.875rem; + cursor: pointer; + transition: color 0.2s ease; + + &:hover { + color: var(--warm-accent-primary-hover); + text-decoration: underline; + } +} diff --git a/src/app/modules/salaries/components/salaries-skills-chart/salaries-skills-chart.component.scss b/src/app/modules/salaries/components/salaries-skills-chart/salaries-skills-chart.component.scss index a55fb6c0..4be8c779 100644 --- a/src/app/modules/salaries/components/salaries-skills-chart/salaries-skills-chart.component.scss +++ b/src/app/modules/salaries/components/salaries-skills-chart/salaries-skills-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// SALARIES SKILLS CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + #canvas { min-height: 400px; @@ -11,6 +20,9 @@ width: 100%; height: 100%; min-height: 400px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; @media (max-width: 768px) { min-height: 300px; @@ -18,5 +30,8 @@ } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/salaries/components/work-industries-chart/work-industries-chart.component.scss b/src/app/modules/salaries/components/work-industries-chart/work-industries-chart.component.scss index d199abae..0a66384f 100644 --- a/src/app/modules/salaries/components/work-industries-chart/work-industries-chart.component.scss +++ b/src/app/modules/salaries/components/work-industries-chart/work-industries-chart.component.scss @@ -1,3 +1,12 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// WORK INDUSTRIES CHART COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + #canvas { min-height: 400px; } @@ -7,8 +16,14 @@ width: 100%; height: 100%; min-height: 400px; + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1rem; } .list-group-item { - font-size: smaller; + font-size: 0.9rem; + background-color: var(--warm-card-bg); + border-color: var(--warm-border-color); + color: var(--warm-text-primary); } diff --git a/src/app/modules/users/components/user-page/user-page.component.scss b/src/app/modules/users/components/user-page/user-page.component.scss index e69de29b..0c341b98 100644 --- a/src/app/modules/users/components/user-page/user-page.component.scss +++ b/src/app/modules/users/components/user-page/user-page.component.scss @@ -0,0 +1,273 @@ +// ═══════════════════════════════════════════════════════════════════════════════ +// USER PAGE COMPONENT STYLES +// Warm & Friendly Theme +// ═══════════════════════════════════════════════════════════════════════════════ + +:host { + display: block; +} + +.user-page { + padding: 1.5rem 0; +} + +.user-header { + background-color: var(--warm-card-bg); + border-radius: 16px; + padding: 2rem; + margin-bottom: 2rem; + box-shadow: var(--warm-shadow-md); + border: 1px solid var(--warm-border-color); + + .user-avatar-section { + display: flex; + align-items: center; + gap: 1.5rem; + margin-bottom: 1.5rem; + + .user-avatar { + width: 100px; + height: 100px; + border-radius: 50%; + background-color: var(--warm-bg-accent); + display: flex; + align-items: center; + justify-content: center; + font-size: 2.5rem; + font-weight: 700; + color: var(--warm-accent-primary); + border: 3px solid var(--warm-accent-primary); + + img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; + } + } + + .user-info { + .user-name { + font-family: var(--warm-font-heading); + font-size: 1.75rem; + font-weight: 700; + color: var(--warm-text-heading); + margin-bottom: 0.25rem; + } + + .user-title { + color: var(--warm-text-secondary); + font-size: 1rem; + } + + .user-joined { + color: var(--warm-text-muted); + font-size: 0.875rem; + margin-top: 0.5rem; + } + } + } + + .user-stats { + display: flex; + gap: 2rem; + padding-top: 1.5rem; + border-top: 1px solid var(--warm-border-color); + + .stat-item { + text-align: center; + + .stat-value { + font-family: var(--warm-font-heading); + font-size: 1.5rem; + font-weight: 700; + color: var(--warm-accent-primary); + } + + .stat-label { + color: var(--warm-text-muted); + font-size: 0.8125rem; + margin-top: 0.25rem; + } + } + } +} + +.user-content { + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; + + @media (min-width: 768px) { + grid-template-columns: 2fr 1fr; + } +} + +.content-section { + background-color: var(--warm-card-bg); + border-radius: 12px; + padding: 1.5rem; + box-shadow: var(--warm-shadow-sm); + border: 1px solid var(--warm-border-color); + + .section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.25rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--warm-border-color); + + h3 { + font-family: var(--warm-font-heading); + font-size: 1.125rem; + color: var(--warm-text-heading); + margin: 0; + } + + .view-all { + color: var(--warm-accent-primary); + font-size: 0.875rem; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } +} + +.activity-list { + .activity-item { + display: flex; + gap: 1rem; + padding: 0.875rem 0; + border-bottom: 1px solid var(--warm-border-color); + + &:last-child { + border-bottom: none; + } + + .activity-icon { + width: 36px; + height: 36px; + border-radius: 8px; + background-color: var(--warm-bg-accent); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + i { + color: var(--warm-accent-primary); + font-size: 0.875rem; + } + } + + .activity-content { + flex: 1; + + .activity-title { + color: var(--warm-text-primary); + font-weight: 500; + margin-bottom: 0.25rem; + + a { + color: var(--warm-accent-primary); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + .activity-date { + color: var(--warm-text-muted); + font-size: 0.75rem; + } + } + } +} + +.templates-list { + .template-item { + padding: 0.875rem; + background-color: var(--warm-bg-tertiary); + border-radius: 8px; + margin-bottom: 0.75rem; + transition: all 0.2s ease; + cursor: pointer; + + &:last-child { + margin-bottom: 0; + } + + &:hover { + background-color: var(--warm-bg-accent); + transform: translateX(4px); + } + + .template-title { + font-weight: 500; + color: var(--warm-text-heading); + margin-bottom: 0.25rem; + } + + .template-meta { + display: flex; + gap: 1rem; + color: var(--warm-text-muted); + font-size: 0.75rem; + } + } +} + +.sidebar-section { + .badge-list { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + + .badge-item { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.75rem; + border-radius: 20px; + background-color: var(--warm-bg-accent); + color: var(--warm-text-secondary); + font-size: 0.8125rem; + + i { + color: var(--warm-accent-tertiary); + } + + &.premium { + background-color: rgba(242, 204, 143, 0.2); + color: var(--warm-accent-tertiary); + } + + &.verified { + background-color: rgba(129, 178, 154, 0.15); + color: var(--warm-success); + } + } + } +} + +.empty-section { + text-align: center; + padding: 2rem 1rem; + color: var(--warm-text-muted); + + i { + font-size: 2rem; + margin-bottom: 0.75rem; + opacity: 0.5; + } + + p { + font-size: 0.875rem; + } +} diff --git a/src/app/services/admin-email.service.ts b/src/app/services/admin-email.service.ts index 2597619d..112551c7 100644 --- a/src/app/services/admin-email.service.ts +++ b/src/app/services/admin-email.service.ts @@ -21,7 +21,12 @@ export class AdminEmailService { constructor(private readonly api: ApiService) {} - sendEmail(request: SendCustomEmailRequest): Observable { - return this.api.post(this.apiUrl + "send", request); + sendEmail( + request: SendCustomEmailRequest, + ): Observable { + return this.api.post( + this.apiUrl + "send", + request, + ); } } diff --git a/src/app/services/currencies-collection.service.ts b/src/app/services/currencies-collection.service.ts index 5eea2fe3..0e204437 100644 --- a/src/app/services/currencies-collection.service.ts +++ b/src/app/services/currencies-collection.service.ts @@ -67,4 +67,3 @@ export class CurrenciesCollectionService { return this.api.get(this.apiUrl + "chart"); } } - diff --git a/src/app/services/interview-templates.service.ts b/src/app/services/interview-templates.service.ts index a787c75e..7ab29c77 100644 --- a/src/app/services/interview-templates.service.ts +++ b/src/app/services/interview-templates.service.ts @@ -19,8 +19,7 @@ export interface InterviewTemplateCreateRequest { labels: Array; } -export interface InterviewTemplateUpdateRequest - extends InterviewTemplateCreateRequest { +export interface InterviewTemplateUpdateRequest extends InterviewTemplateCreateRequest { id: string; } diff --git a/src/app/services/meta-tags.service.ts b/src/app/services/meta-tags.service.ts index 3624439e..19c9c9da 100644 --- a/src/app/services/meta-tags.service.ts +++ b/src/app/services/meta-tags.service.ts @@ -14,8 +14,8 @@ export interface CompanyMetaTags { providedIn: "root", }) export class MetaTagService { - - public static readonly DEFAULT_IMAGE_URL = "https://techinterview.fra1.cdn.digitaloceanspaces.com/images/main_charts_500.png"; + public static readonly DEFAULT_IMAGE_URL = + "https://techinterview.fra1.cdn.digitaloceanspaces.com/images/main_charts_500.png"; constructor( private readonly title: TitleService, @@ -117,7 +117,8 @@ export class MetaTagService { description: string, url: string, imageUrl?: string | null, - addTitlePostfix: boolean = true): void { + addTitlePostfix: boolean = true, + ): void { const fullUrl = url.startsWith("/") ? environment.baseUrl + url : url; if (addTitlePostfix) { diff --git a/src/app/services/salaries-historical-data-templates.service.ts b/src/app/services/salaries-historical-data-templates.service.ts index 6a46e36d..5718fed7 100644 --- a/src/app/services/salaries-historical-data-templates.service.ts +++ b/src/app/services/salaries-historical-data-templates.service.ts @@ -66,4 +66,3 @@ export class SalariesHistoricalDataTemplatesService { return this.api.delete(`${this.apiUrl}/${id}/records`, {}); } } - diff --git a/src/app/services/telegram-bot.service.ts b/src/app/services/telegram-bot.service.ts index 397a4375..9cffda37 100644 --- a/src/app/services/telegram-bot.service.ts +++ b/src/app/services/telegram-bot.service.ts @@ -14,8 +14,7 @@ export interface UpdateTelegramUserSettingsBody { sendBotRegularStatsUpdates: boolean; } -export interface CreateTelegramUserSettingsBody - extends UpdateTelegramUserSettingsBody { +export interface CreateTelegramUserSettingsBody extends UpdateTelegramUserSettingsBody { chatId: number; userId: number; username: string; diff --git a/src/app/services/telegram-subscriptions.service.ts b/src/app/services/telegram-subscriptions.service.ts index 416f167e..da077ffc 100644 --- a/src/app/services/telegram-subscriptions.service.ts +++ b/src/app/services/telegram-subscriptions.service.ts @@ -10,8 +10,7 @@ import { } from "@models/telegram"; import { OpenAiAnalysis, OpenAiReport } from "@models/open-ai.model"; -export interface CreateTelegramSubscriptionBody - extends EditTelegramSubscriptionBody { +export interface CreateTelegramSubscriptionBody extends EditTelegramSubscriptionBody { telegramChatId: number; } diff --git a/src/app/shared/components/app-spinner/app-spinner.component.html b/src/app/shared/components/app-spinner/app-spinner.component.html index 07682970..72a9de88 100644 --- a/src/app/shared/components/app-spinner/app-spinner.component.html +++ b/src/app/shared/components/app-spinner/app-spinner.component.html @@ -10,4 +10,3 @@ - diff --git a/src/app/shared/components/app-spinner/app-spinner.component.scss b/src/app/shared/components/app-spinner/app-spinner.component.scss index e37c7637..46ca7a8a 100644 --- a/src/app/shared/components/app-spinner/app-spinner.component.scss +++ b/src/app/shared/components/app-spinner/app-spinner.component.scss @@ -36,4 +36,3 @@ transform: rotate(360deg); } } - diff --git a/src/app/shared/components/app-spinner/app-spinner.component.spec.ts b/src/app/shared/components/app-spinner/app-spinner.component.spec.ts index 4fe1866b..836a6e70 100644 --- a/src/app/shared/components/app-spinner/app-spinner.component.spec.ts +++ b/src/app/shared/components/app-spinner/app-spinner.component.spec.ts @@ -12,7 +12,9 @@ describe("AppSpinnerComponent", () => { beforeEach(async () => { visibilitySubject = new BehaviorSubject(false); spinnerService = jasmine.createSpyObj("SpinnerService", ["getVisibility$"]); - spinnerService.getVisibility$.and.returnValue(visibilitySubject.asObservable()); + spinnerService.getVisibility$.and.returnValue( + visibilitySubject.asObservable(), + ); await TestBed.configureTestingModule({ declarations: [AppSpinnerComponent], @@ -40,4 +42,3 @@ describe("AppSpinnerComponent", () => { expect(component.isVisible).toBe(false); }); }); - diff --git a/src/app/shared/components/app-spinner/app-spinner.component.ts b/src/app/shared/components/app-spinner/app-spinner.component.ts index 1be85ab5..b0871da6 100644 --- a/src/app/shared/components/app-spinner/app-spinner.component.ts +++ b/src/app/shared/components/app-spinner/app-spinner.component.ts @@ -30,4 +30,3 @@ export class AppSpinnerComponent implements OnInit, OnDestroy { ngOnDestroy(): void {} } - diff --git a/src/app/shared/components/dialogs/dialog/dialog.component.scss b/src/app/shared/components/dialogs/dialog/dialog.component.scss index ab7b8a30..1c0e5b1a 100644 --- a/src/app/shared/components/dialogs/dialog/dialog.component.scss +++ b/src/app/shared/components/dialogs/dialog/dialog.component.scss @@ -1,5 +1,2 @@ -.modal { - background-color: rgba(0, 0, 0, 0.6); - -webkit-animation: fadein 0.2s; - animation: fadein 0.2s; -} +// Dialog component - uses global .modal styles from styles.scss +// No component-specific overrides needed diff --git a/src/app/shared/services/theme/theme.service.ts b/src/app/shared/services/theme/theme.service.ts new file mode 100644 index 00000000..5bcbba0f --- /dev/null +++ b/src/app/shared/services/theme/theme.service.ts @@ -0,0 +1,276 @@ +import { + Injectable, + signal, + computed, + effect, + PLATFORM_ID, + Inject, +} from "@angular/core"; +import { isPlatformBrowser } from "@angular/common"; + +/** + * Theme options for the application + */ +export type Theme = "light" | "dark" | "system"; + +/** + * The actual resolved theme (what's displayed) + */ +export type ResolvedTheme = "light" | "dark"; + +/** + * Local storage key for persisting theme preference + */ +const THEME_STORAGE_KEY = "theme"; + +/** + * ThemeService manages the application's light/dark mode theming. + * + * Features: + * - Supports light, dark, and system (auto) themes + * - Persists user preference in localStorage + * - Automatically detects and responds to system preference changes + * - Uses Angular signals for reactive state management + * - Prevents flash of unstyled content (FOUC) via index.html script + * + * Usage: + * ```typescript + * // In component + * constructor(public themeService: ThemeService) {} + * + * // Toggle between themes + * themeService.toggleTheme(); + * + * // Set specific theme + * themeService.setTheme('dark'); + * + * // Get current theme + * const theme = themeService.theme(); + * const isDark = themeService.isDarkMode(); + * ``` + */ +@Injectable({ + providedIn: "root", +}) +export class ThemeService { + private readonly isBrowser: boolean; + + /** + * The user's theme preference (light, dark, or system) + */ + private readonly _theme = signal("system"); + + /** + * Whether the system prefers dark mode + */ + private readonly _systemPrefersDark = signal(false); + + /** + * Public readonly access to the theme preference + */ + readonly theme = this._theme.asReadonly(); + + /** + * The resolved theme that's actually being displayed + */ + readonly resolvedTheme = computed(() => { + const theme = this._theme(); + if (theme === "system") { + return this._systemPrefersDark() ? "dark" : "light"; + } + return theme; + }); + + /** + * Whether dark mode is currently active + */ + readonly isDarkMode = computed(() => this.resolvedTheme() === "dark"); + + /** + * Whether light mode is currently active + */ + readonly isLightMode = computed(() => this.resolvedTheme() === "light"); + + /** + * Whether the theme is set to follow system preference + */ + readonly isSystemTheme = computed(() => this._theme() === "system"); + + /** + * Media query for detecting system dark mode preference + */ + private mediaQuery: MediaQueryList | null = null; + + constructor(@Inject(PLATFORM_ID) platformId: object) { + this.isBrowser = isPlatformBrowser(platformId); + + if (this.isBrowser) { + // Initialize system preference detection + this.initSystemPreferenceDetection(); + + // Load saved theme from localStorage + this.loadSavedTheme(); + + // Set up effect to apply theme changes to DOM + effect(() => { + this.applyTheme(this.resolvedTheme()); + }); + } + } + + /** + * Sets up detection and listening for system color scheme preference + */ + private initSystemPreferenceDetection(): void { + this.mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + this._systemPrefersDark.set(this.mediaQuery.matches); + + // Listen for system preference changes + const handler = (event: MediaQueryListEvent) => { + this._systemPrefersDark.set(event.matches); + }; + + // Modern browsers + if (this.mediaQuery.addEventListener) { + this.mediaQuery.addEventListener("change", handler); + } else { + // Fallback for older browsers + this.mediaQuery.addListener(handler); + } + } + + /** + * Loads the saved theme preference from localStorage + */ + private loadSavedTheme(): void { + const savedTheme = localStorage.getItem(THEME_STORAGE_KEY) as Theme | null; + + if (savedTheme && ["light", "dark", "system"].includes(savedTheme)) { + this._theme.set(savedTheme); + } else { + // Default to system preference + this._theme.set("system"); + } + } + + /** + * Applies the theme to the DOM by setting data-theme attribute + */ + private applyTheme(theme: ResolvedTheme): void { + if (!this.isBrowser) return; + + const root = document.documentElement; + + // Remove existing theme attribute if setting to light (default) + if (theme === "light" && this._theme() === "system") { + root.removeAttribute("data-theme"); + } else { + root.setAttribute("data-theme", theme); + } + + // Update meta theme-color for mobile browsers + this.updateMetaThemeColor(theme); + } + + /** + * Updates the meta theme-color tag for mobile browser chrome + */ + private updateMetaThemeColor(theme: ResolvedTheme): void { + const metaThemeColor = document.querySelector( + 'meta[name="theme-color"]:not([media])', + ); + if (metaThemeColor) { + metaThemeColor.setAttribute( + "content", + theme === "dark" ? "#1a1816" : "#fffbf7", + ); + } + } + + /** + * Sets the theme preference + * @param theme - The theme to set ('light', 'dark', or 'system') + */ + setTheme(theme: Theme): void { + this._theme.set(theme); + + if (this.isBrowser) { + // Persist to localStorage + if (theme === "system") { + localStorage.removeItem(THEME_STORAGE_KEY); + } else { + localStorage.setItem(THEME_STORAGE_KEY, theme); + } + } + } + + /** + * Toggles between light and dark themes + * If currently on system theme, switches to the opposite of the current resolved theme + */ + toggleTheme(): void { + const currentResolved = this.resolvedTheme(); + const newTheme: Theme = currentResolved === "dark" ? "light" : "dark"; + this.setTheme(newTheme); + } + + /** + * Cycles through themes: light -> dark -> system -> light + */ + cycleTheme(): void { + const current = this._theme(); + const cycle: Theme[] = ["light", "dark", "system"]; + const currentIndex = cycle.indexOf(current); + const nextIndex = (currentIndex + 1) % cycle.length; + this.setTheme(cycle[nextIndex]); + } + + /** + * Resets theme to follow system preference + */ + resetToSystem(): void { + this.setTheme("system"); + } + + /** + * Gets an icon name for the current theme state (for use with Bootstrap Icons) + */ + getThemeIcon(): string { + const theme = this._theme(); + switch (theme) { + case "light": + return "bi-sun-fill"; + case "dark": + return "bi-moon-fill"; + case "system": + return "bi-circle-half"; + default: + return "bi-circle-half"; + } + } + + /** + * Gets a label for the current theme (for accessibility and tooltips) + */ + getThemeLabel(): string { + const theme = this._theme(); + switch (theme) { + case "light": + return "Светлая тема"; + case "dark": + return "Тёмная тема"; + case "system": + return "Системная тема"; + default: + return "Переключить тему"; + } + } + + /** + * Gets the label for the next theme in the toggle cycle + */ + getNextThemeLabel(): string { + const currentResolved = this.resolvedTheme(); + return currentResolved === "dark" ? "Светлая тема" : "Тёмная тема"; + } +} diff --git a/src/index.html b/src/index.html index cc109315..6637fa40 100644 --- a/src/index.html +++ b/src/index.html @@ -1,5 +1,5 @@ - + Techinterview.space @@ -21,6 +21,17 @@ content="salaries, statistic, technical interview, interview, coding" /> + + + + + + + + + + + + @@ -75,7 +124,7 @@ - Наши проекты + - Ссылки + diff --git a/src/styles.scss b/src/styles.scss index 22fbf22a..849ed21a 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,27 +1,174 @@ -/* You can add global styles to this file, and also import other style files */ -/* Importing Bootstrap SCSS file. */ +/* ═══════════════════════════════════════════════════════════════════════════════ + TECHINTERVIEW.SPACE - Global Styles + Warm & Friendly Theme with Dark/Light Mode Support + ═══════════════════════════════════════════════════════════════════════════════ */ + +/* Importing dependencies */ @use "sass:color"; @import "bootstrap/scss/bootstrap"; @import "bootstrap-icons/font/bootstrap-icons.css"; @import "_colors"; +@import "_warm-theme"; + +/* ───────────────────────────────────────────────────────────────────────────────── + BASE STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +html { + scroll-behavior: smooth; +} + +body { + background-color: var(--warm-bg-primary); + color: var(--warm-text-primary); + font-family: $font-body; + font-size: 1rem; + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: radial-gradient( + circle, + var(--warm-text-primary) 1px, + transparent 1px + ); + background-size: 20px 20px; + opacity: 0.15; + pointer-events: none; + z-index: -1; +} + +/* Headings */ +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: $font-heading; + color: var(--warm-text-heading); + font-weight: 600; + line-height: 1.3; + margin-bottom: 0.5em; +} + +.footer-link-header { + color: var(--warm-footer-text); +} + +.display-1, +.display-2, +.display-3, +.display-4, +.display-5, +.display-6 { + font-family: "Lora", Georgia, serif; + font-weight: 400; + letter-spacing: 0.02em; + color: var(--warm-text-heading); +} + +/* Paragraphs */ +p { + color: var(--warm-text-primary); + margin-bottom: 1rem; +} + +/* Links */ +a { + color: var(--warm-accent-primary); + text-decoration: none; + transition: color $transition-fast; + + &:hover { + color: var(--warm-accent-primary-hover); + text-decoration: underline; + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + FOOTER STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +/* Sticky footer layout - footer stays at bottom */ +.footer { + flex-shrink: 0; +} .footer-wrapper { - background-color: $bg-petrel-dark; - color: rgb(240, 240, 240); + background-color: var(--warm-footer-bg); + color: var(--warm-footer-text); } +.footer-link { + color: var(--warm-footer-text) !important; + text-decoration: none; + transition: + color $transition-fast, + opacity $transition-fast; + + &:hover { + color: var(--warm-accent-primary) !important; + text-decoration: underline; + opacity: 0.9; + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + LEGACY BACKGROUND CLASSES (maintaining backward compatibility) + ───────────────────────────────────────────────────────────────────────────────── */ + .bg-petrel-light { - background-color: $bg-petrel-light; + background-color: var(--warm-bg-secondary); } .bg-petrel-primary { - background-color: $bg-petrel-primary; + background-color: var(--warm-accent-primary); } .bg-petrel-dark { - background-color: $bg-petrel-dark; + background-color: var(--warm-bg-inverse); +} + +/* New warm background utility classes */ +.bg-warm-primary { + background-color: var(--warm-bg-primary); } +.bg-warm-secondary { + background-color: var(--warm-bg-secondary); +} + +.bg-warm-tertiary { + background-color: var(--warm-bg-tertiary); +} + +.bg-warm-accent { + background-color: var(--warm-bg-accent); +} + +.bg-warm-elevated { + background-color: var(--warm-bg-elevated); +} + +/* ───────────────────────────────────────────────────────────────────────────────── + FORM VALIDATION STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + input.ng-touched.ng-invalid { @extend .is-invalid; } @@ -30,129 +177,403 @@ input.ng-touched.ng-valid { @extend .is-valid; } +/* Form input styling */ +.form-control, +.form-select { + background-color: var(--warm-input-bg); + border-color: var(--warm-input-border); + color: var(--warm-text-primary); + border-radius: $radius-md; + transition: + border-color $transition-fast, + box-shadow $transition-fast; + + &:focus { + background-color: var(--warm-input-bg); + border-color: var(--warm-input-focus-border); + box-shadow: 0 0 0 3px var(--warm-input-focus-ring); + color: var(--warm-text-primary); + } + + &::placeholder { + color: var(--warm-text-muted); + } + + &:disabled { + background-color: var(--warm-bg-accent); + color: var(--warm-text-muted); + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + MODAL STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + .modal { - background-color: rgba(0, 0, 0, 0.6); + background-color: var(--warm-modal-overlay); -webkit-animation: fadein 0.2s; animation: fadein 0.2s; } -body { - background-color: $bg-petrel-light; +.modal-content { + background-color: var(--warm-modal-bg); + border: 1px solid var(--warm-border-color); + border-radius: $radius-lg; + box-shadow: $shadow-xl; + color: var(--warm-text-primary); } -.footer-link { - color: $bg-petrel-light !important; - text-decoration: none; +.modal-header { + border-bottom: 1px solid var(--warm-divider); +} - &:hover { - color: color.adjust($bg-petrel-light, $lightness: -20%) !important; - text-decoration: underline; - } +.modal-footer { + border-top: 1px solid var(--warm-divider); } +/* ───────────────────────────────────────────────────────────────────────────────── + LINK STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + .link-as-text { color: inherit; text-decoration: none; &:hover { - color: color.adjust($bg-petrel-dark, $lightness: -20%) !important; + color: var(--warm-accent-primary-hover) !important; text-decoration: underline; } } +/* ───────────────────────────────────────────────────────────────────────────────── + BUTTON STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + .btn-huge { padding: 0.8rem 1.6rem; font-size: 1.6rem; - border-radius: 0.5rem; + border-radius: $radius-md; } -ul.no-bullets { - list-style-type: none; /* Remove bullets */ - padding: 0; /* Remove padding */ - margin: 0; /* Remove margins */ +/* Primary button - Terracotta accent */ +.btn-primary, +.btn-purple { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + border-radius: $radius-md; + font-weight: 600; + transition: + background-color $transition-fast, + transform $transition-fast, + box-shadow $transition-fast; + + &:hover:not(:disabled) { + background-color: var(--warm-accent-primary-hover); + border-color: var(--warm-accent-primary-hover); + color: white; + transform: translateY(-1px); + box-shadow: $shadow-md; + } + + &:active { + transform: translateY(0); + } + + &:focus { + box-shadow: 0 0 0 3px var(--warm-input-focus-ring); + } + + &:disabled, + &.disabled { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + opacity: 0.5; + cursor: not-allowed; + } +} + +/* Secondary button - Sage accent */ +.btn-secondary { + background-color: var(--warm-accent-secondary); + border-color: var(--warm-accent-secondary); + color: white; + border-radius: $radius-md; + font-weight: 600; + + &:hover:not(:disabled) { + background-color: var(--warm-accent-secondary-hover); + border-color: var(--warm-accent-secondary-hover); + color: white; + } + + &:disabled, + &.disabled { + background-color: var(--warm-accent-secondary); + border-color: var(--warm-accent-secondary); + color: white; + opacity: 0.5; + cursor: not-allowed; + } +} + +/* Outline buttons */ +.btn-outline-primary { + color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + border-radius: $radius-md; + + &:hover:not(:disabled) { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + } + + &:disabled, + &.disabled { + color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + opacity: 0.5; + cursor: not-allowed; + } +} + +.btn-outline-secondary { + color: var(--warm-accent-secondary); + border-color: var(--warm-accent-secondary); + border-radius: $radius-md; + + &:hover:not(:disabled) { + background-color: var(--warm-accent-secondary); + border-color: var(--warm-accent-secondary); + color: white; + } + + &:disabled, + &.disabled { + color: var(--warm-accent-secondary); + border-color: var(--warm-accent-secondary); + opacity: 0.5; + cursor: not-allowed; + } } +/* Link-style buttons */ .btn-link-sm { color: inherit; background-color: transparent; border: 0; font-weight: 600; - /*font-size: 0.875rem;*/ text-decoration: none; + transition: color $transition-fast; &:disabled { - color: $color-grey; + color: var(--warm-text-muted); + &:hover { text-decoration: none; } } &:hover { - color: color.adjust($color-dark, $lightness: -20%); + color: var(--warm-accent-primary-hover); text-decoration: underline; } &.btn-link-danger { - color: $color-danger; + color: var(--warm-danger); + &:hover { - color: color.adjust($color-danger, $lightness: -20%); + color: color.adjust(#d95550, $lightness: -10%); } } &.btn-link-warning { - color: $color-warning; + color: var(--warm-warning); + &:hover { - color: color.adjust($color-warning, $lightness: -20%); + color: color.adjust(#e9a820, $lightness: -10%); } } &.btn-link-success { - color: $color-success; + color: var(--warm-success); + &:hover { - color: color.adjust($color-success, $lightness: -20%); + color: color.adjust(#81b29a, $lightness: -10%); } } &.btn-link-info { - color: $color-info; + color: var(--warm-info); + &:hover { - color: color.adjust($color-info, $lightness: -20%); + color: color.adjust(#6fa8c7, $lightness: -10%); font-weight: 600; } } } +/* ───────────────────────────────────────────────────────────────────────────────── + LIST STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +ul.no-bullets { + list-style-type: none; + padding: 0; + margin: 0; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + CARD STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.card { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: $radius-lg; + box-shadow: $shadow-sm; + color: var(--warm-text-primary); + transition: + box-shadow $transition-normal, + transform $transition-normal; + + &:hover { + box-shadow: $shadow-md; + } +} + +.card-header { + background-color: var(--warm-bg-secondary); + border-bottom: 1px solid var(--warm-divider); +} + +.card-footer { + background-color: var(--warm-bg-secondary); + border-top: 1px solid var(--warm-divider); +} + +/* ───────────────────────────────────────────────────────────────────────────────── + TABLE STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.table { + color: var(--warm-text-primary); + + > :not(caption) > * > * { + background-color: transparent; + border-bottom-color: var(--warm-table-border); + color: var(--warm-text-primary); + } + + > tbody > tr:nth-of-type(odd) > * { + background-color: var(--warm-table-stripe); + } + + > tbody > tr:hover > * { + background-color: var(--warm-table-hover); + } + + th { + font-family: $font-heading; + font-weight: 600; + color: var(--warm-text-heading); + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + ALERT STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.alert-danger { + background-color: var(--warm-danger-bg); + border-color: var(--warm-danger); + color: var(--warm-danger); +} + +.alert-warning { + background-color: var(--warm-warning-bg); + border-color: var(--warm-warning); + color: color.adjust(#e9a820, $lightness: -20%); +} + +.alert-success { + background-color: var(--warm-success-bg); + border-color: var(--warm-success); + color: color.adjust(#81b29a, $lightness: -20%); +} + +.alert-info { + background-color: var(--warm-info-bg); + border-color: var(--warm-info); + color: color.adjust(#6fa8c7, $lightness: -20%); +} + +/* ───────────────────────────────────────────────────────────────────────────────── + BADGE STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.badge { + font-family: $font-body; + font-weight: 600; + border-radius: $radius-full; + padding: 0.35em 0.65em; +} + +.badge.bg-primary { + background-color: var(--warm-accent-primary) !important; +} + +.badge.bg-secondary { + background-color: var(--warm-accent-secondary) !important; +} + +.badge.bg-danger { + background-color: var(--warm-danger) !important; +} + +.badge.bg-warning { + background-color: var(--warm-warning) !important; + color: #2d2d2d; +} + +.badge.bg-success { + background-color: var(--warm-success) !important; +} + +.badge.bg-info { + background-color: var(--warm-info) !important; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + SPACING UTILITIES + ───────────────────────────────────────────────────────────────────────────────── */ + .mt-7 { - margin-top: 3.75rem; + margin-top: $space-7; } .mt-8 { - margin-top: 4rem; + margin-top: $space-8; } .mt-9 { - margin-top: 4.5rem; + margin-top: $space-9; } .mt-10 { - margin-top: 5.5rem; + margin-top: $space-10; } .px-10 { - padding-left: 5.5rem; - padding-right: 5.5rem; + padding-left: $space-10; + padding-right: $space-10; } -.btn-purple { - background-color: $color-purple; - color: white; - border: none; - - &:hover { - background-color: $color-purple-darkest; - color: white; - } -} +/* ───────────────────────────────────────────────────────────────────────────────── + WIDTH UTILITIES + ───────────────────────────────────────────────────────────────────────────────── */ .w-30 { width: 30%; @@ -162,6 +583,10 @@ ul.no-bullets { width: 80%; } +/* ───────────────────────────────────────────────────────────────────────────────── + MOBILE RESPONSIVE UTILITIES + ───────────────────────────────────────────────────────────────────────────────── */ + .mobile-mb-3 { margin-bottom: inherit; } @@ -200,17 +625,702 @@ ul.no-bullets { } } +/* ───────────────────────────────────────────────────────────────────────────────── + INTERACTIVE EFFECTS + ───────────────────────────────────────────────────────────────────────────────── */ + .increasable-on-hover { - transition: all 0.3s ease; + transition: + transform $transition-normal, + box-shadow $transition-normal; cursor: pointer; &:hover { transform: scale(1.05); - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + box-shadow: $shadow-md; + } +} + +/* Subtle lift effect for cards and interactive elements */ +.hover-lift { + transition: + transform $transition-normal, + box-shadow $transition-normal; + + &:hover { + transform: translateY(-4px); + box-shadow: $shadow-lg; + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + TEXT UTILITIES + ───────────────────────────────────────────────────────────────────────────────── */ + +.text-warm-primary { + color: var(--warm-text-primary); +} + +.text-warm-secondary { + color: var(--warm-text-secondary); +} + +.text-warm-muted { + color: var(--warm-text-muted); +} + +.text-warm-heading { + color: var(--warm-text-heading); +} + +.text-accent-primary { + color: var(--warm-accent-primary); +} + +.text-accent-secondary { + color: var(--warm-accent-secondary); +} + +/* Bootstrap text utility overrides for dark mode */ +.text-muted { + color: var(--warm-text-muted) !important; +} + +.text-dark { + color: var(--warm-text-primary) !important; +} + +.text-secondary { + color: var(--warm-text-secondary) !important; +} + +/* Bootstrap background utility overrides for dark mode */ +.bg-light { + background-color: var(--warm-bg-accent) !important; + color: var(--warm-text-primary) !important; +} + +.bg-white { + background-color: var(--warm-card-bg) !important; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + SHADOW UTILITIES + ───────────────────────────────────────────────────────────────────────────────── */ + +.shadow-warm-sm { + box-shadow: $shadow-sm; +} + +.shadow-warm-md { + box-shadow: $shadow-md; +} + +.shadow-warm-lg { + box-shadow: $shadow-lg; +} + +.shadow-warm-xl { + box-shadow: $shadow-xl; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + CODE STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +code { + background-color: var(--warm-code-bg); + color: var(--warm-code-text); + border: 1px solid var(--warm-code-border); + border-radius: $radius-sm; + padding: 0.15em 0.4em; + font-family: $font-mono; + font-size: 0.875em; +} + +pre { + background-color: var(--warm-code-bg); + border: 1px solid var(--warm-code-border); + border-radius: $radius-md; + padding: 1rem; + overflow-x: auto; + + code { + background-color: transparent; + border: none; + padding: 0; + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + DIVIDER STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +hr { + border-color: var(--warm-divider); + opacity: 1; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + DROPDOWN STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.dropdown-menu { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-border-color); + border-radius: $radius-md; + box-shadow: $shadow-lg; +} + +.dropdown-item { + color: var(--warm-text-primary); + transition: background-color $transition-fast; + + &:hover, + &:focus { + background-color: var(--warm-bg-accent); + color: var(--warm-text-primary); + } + + &.active, + &:active { + background-color: var(--warm-accent-primary); + color: white; + } +} + +.dropdown-divider { + border-color: var(--warm-divider); +} + +/* ───────────────────────────────────────────────────────────────────────────────── + NAV/TAB STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.nav-tabs { + border-bottom-color: var(--warm-border-color); + + .nav-link { + color: var(--warm-text-secondary); + border-radius: $radius-md $radius-md 0 0; + transition: + color $transition-fast, + background-color $transition-fast; + + &:hover { + color: var(--warm-accent-primary); + border-color: transparent; + } + + &.active { + color: var(--warm-accent-primary); + background-color: var(--warm-bg-primary); + border-color: var(--warm-border-color) var(--warm-border-color) + var(--warm-bg-primary); + } + } +} + +.nav-pills { + .nav-link { + color: var(--warm-text-secondary); + border-radius: $radius-md; + + &:hover { + background-color: var(--warm-bg-accent); + color: var(--warm-text-primary); + } + + &.active { + background-color: var(--warm-accent-primary); + color: white; + } + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + PAGINATION STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.pagination { + .page-link { + color: var(--warm-text-primary); + background-color: var(--warm-bg-elevated); + border-color: var(--warm-border-color); + border-radius: $radius-md; + margin: 0 2px; + transition: all $transition-fast; + + &:hover { + background-color: var(--warm-bg-accent); + border-color: var(--warm-accent-primary); + color: var(--warm-accent-primary); + } + } + + .page-item.active .page-link { + background-color: var(--warm-accent-primary); + border-color: var(--warm-accent-primary); + color: white; + } + + .page-item.disabled .page-link { + color: var(--warm-text-muted); + background-color: var(--warm-bg-secondary); + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + BREADCRUMB STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.breadcrumb { + background-color: transparent; + + .breadcrumb-item { + color: var(--warm-text-secondary); + + a { + color: var(--warm-accent-primary); + + &:hover { + color: var(--warm-accent-primary-hover); + } + } + + &.active { + color: var(--warm-text-muted); + } + + + .breadcrumb-item::before { + color: var(--warm-text-muted); + } + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + TOOLTIP STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.tooltip { + .tooltip-inner { + background-color: var(--warm-bg-inverse); + color: var(--warm-text-inverse); + border-radius: $radius-sm; + font-family: $font-body; + font-size: 0.875rem; + padding: 0.5rem 0.75rem; + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + CHART & STATS COMPONENTS + ───────────────────────────────────────────────────────────────────────────────── */ + +.chart-card { + background-color: var(--warm-card-bg); + border: 1px solid var(--warm-card-border); + border-radius: $radius-lg; + box-shadow: $shadow-sm; + padding: 1.5rem; + margin-bottom: 2rem; + transition: box-shadow $transition-normal; + + &:hover { + box-shadow: $shadow-lg; + } +} + +.chart-title { + font-family: $font-heading; + font-weight: 600; + font-size: 1.25rem; + color: var(--warm-text-heading); + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + + i { + color: var(--warm-accent-primary); + } +} + +.chapter-title { + font-family: $font-heading; + font-weight: 600; + color: var(--warm-text-heading); + margin-top: 4rem; + margin-bottom: 1.5rem; + padding-bottom: 0.75rem; + border-bottom: 2px solid var(--warm-accent-primary); + display: inline-block; +} + +.stats-card { + background: linear-gradient( + 135deg, + var(--warm-bg-elevated) 0%, + var(--warm-bg-secondary) 100% + ); + border: 1px solid var(--warm-card-border); + border-radius: $radius-md; + padding: 1.25rem; + text-align: center; + transition: + transform $transition-fast, + box-shadow $transition-fast; + + &:hover { + transform: translateY(-2px); + box-shadow: $shadow-md; + } +} + +.stats-value { + font-family: $font-heading; + font-size: 2rem; + font-weight: 700; + color: var(--warm-accent-primary); + line-height: 1.2; +} + +.stats-label { + font-size: 0.875rem; + color: var(--warm-text-secondary); + margin-top: 0.25rem; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + INFO & WARNING BOXES + ───────────────────────────────────────────────────────────────────────────────── */ + +.info-box { + background-color: var(--warm-info-bg); + border: 1px solid var(--warm-info); + border-radius: $radius-md; + padding: 1rem 1.25rem; + margin-bottom: 1rem; + color: var(--warm-text-primary); + + i { + color: var(--warm-info); + } +} + +.warning-box { + background-color: var(--warm-warning-bg); + border: 1px solid var(--warm-warning); + border-radius: $radius-md; + padding: 1rem 1.25rem; + margin-bottom: 1rem; + color: var(--warm-text-primary); + + i { + color: var(--warm-warning); + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + BLUR EFFECT + ───────────────────────────────────────────────────────────────────────────────── */ + +.blur { + -webkit-filter: blur(6px); + filter: blur(6px); + pointer-events: none; + user-select: none; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + OUTLINE BUTTON VARIANTS + ───────────────────────────────────────────────────────────────────────────────── */ + +.btn-outline-success { + color: var(--warm-accent-secondary); + border-color: var(--warm-accent-secondary); + border-width: 2px; + border-radius: $radius-md; + + &:hover { + background-color: var(--warm-accent-secondary); + border-color: var(--warm-accent-secondary); + color: white; + } +} + +.btn-outline-dark { + color: var(--warm-text-primary); + border-color: var(--warm-border-strong); + border-width: 2px; + border-radius: $radius-md; + + &:hover { + background-color: var(--warm-bg-inverse); + border-color: var(--warm-bg-inverse); + color: var(--warm-text-inverse); + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + NAVBAR STYLES + ───────────────────────────────────────────────────────────────────────────────── */ + +.navbar { + background-color: var(--warm-navbar-bg); + border-color: var(--warm-navbar-border) !important; + box-shadow: + 0 2px 8px rgba(0, 0, 0, 0.08), + 0 4px 16px rgba(0, 0, 0, 0.04); + transition: + background-color $transition-slow, + border-color $transition-slow, + box-shadow $transition-slow; +} + +.navbar-brand { + font-family: $font-heading; + font-weight: 600; + color: var(--warm-text-heading); + display: flex; + align-items: center; + transition: color $transition-fast; + + &:hover { + color: var(--warm-accent-primary); + text-decoration: none; + } +} + +.nav-link { + color: var(--warm-text-secondary); + font-weight: 500; + padding: 0.5rem 0.75rem; + border-radius: $radius-sm; + transition: + color $transition-fast, + background-color $transition-fast; + + &:hover { + color: var(--warm-accent-primary); + background-color: var(--warm-bg-accent); + } +} + +.navbar-toggler { + border-color: var(--warm-border-color); + border-radius: $radius-sm; + transition: background-color $transition-fast; + + &:hover { + background-color: var(--warm-bg-accent); + } + + &:focus { + box-shadow: 0 0 0 3px var(--warm-input-focus-ring); + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + STATUS BADGES + ───────────────────────────────────────────────────────────────────────────────── */ + +.health-status { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.25rem 0.75rem; + border-radius: $radius-full; + font-size: 0.75rem; + font-weight: 600; + + &.status-healthy { + background-color: rgba(129, 178, 154, 0.15); + color: var(--warm-success); + } + + &.status-unhealthy { + background-color: rgba(217, 85, 80, 0.15); + color: var(--warm-danger); + } + + &.status-degraded { + background-color: rgba(242, 204, 143, 0.2); + color: var(--warm-warning); + } + + .status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: currentColor; + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + CURSOR UTILITIES + ───────────────────────────────────────────────────────────────────────────────── */ + +.cursor-pointer { + cursor: pointer; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + ANIMATION KEYFRAMES + ───────────────────────────────────────────────────────────────────────────────── */ + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fadeInUp 0.6s ease-out; +} + +@keyframes fadein { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); } + + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ───────────────────────────────────────────────────────────────────────────────── + NG-SELECT THEME OVERRIDES + ───────────────────────────────────────────────────────────────────────────────── */ + +.ng-select .ng-select-container { + background-color: var(--warm-input-bg) !important; + border-color: var(--warm-input-border) !important; + color: var(--warm-text-primary) !important; +} + +.ng-select.ng-select-focused .ng-select-container { + border-color: var(--warm-input-focus-border) !important; + box-shadow: 0 0 0 3px var(--warm-input-focus-ring) !important; +} + +.ng-select .ng-select-container .ng-value-container .ng-value { + color: var(--warm-text-primary) !important; +} + +.ng-select .ng-select-container .ng-value-container .ng-placeholder { + color: var(--warm-text-muted) !important; +} + +.ng-select .ng-select-container .ng-value-container .ng-input > input { + color: var(--warm-text-primary) !important; } -/* ng-select bootstrap-like styles. Start */ +.ng-select .ng-arrow-wrapper .ng-arrow { + border-color: var(--warm-text-muted) transparent transparent !important; +} + +.ng-select .ng-clear-wrapper { + color: var(--warm-text-muted) !important; +} + +.ng-select .ng-dropdown-panel { + background-color: var(--warm-card-bg) !important; + border: 1px solid var(--warm-border-color) !important; + box-shadow: + 0 4px 8px var(--warm-shadow-color), + 0 8px 16px var(--warm-shadow-color) !important; +} + +.ng-select .ng-dropdown-panel .ng-dropdown-header { + background-color: var(--warm-bg-secondary) !important; + border-bottom: 1px solid var(--warm-border-color) !important; + color: var(--warm-text-primary) !important; +} + +.ng-select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option { + background-color: var(--warm-card-bg) !important; + color: var(--warm-text-primary) !important; +} + +.ng-select .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:hover, +.ng-select + .ng-dropdown-panel + .ng-dropdown-panel-items + .ng-option.ng-option-marked { + background-color: var(--warm-bg-accent) !important; + color: var(--warm-text-primary) !important; +} + +.ng-select + .ng-dropdown-panel + .ng-dropdown-panel-items + .ng-option.ng-option-selected, +.ng-select + .ng-dropdown-panel + .ng-dropdown-panel-items + .ng-option.ng-option-selected.ng-option-marked { + background-color: var(--warm-accent-primary) !important; + color: white !important; +} + +.ng-select + .ng-dropdown-panel + .ng-dropdown-panel-items + .ng-option.ng-option-disabled { + color: var(--warm-text-muted) !important; +} + +.ng-select.ng-select-disabled .ng-select-container { + background-color: var(--warm-bg-accent) !important; + color: var(--warm-text-muted) !important; +} + +.ng-select.ng-select-multiple + .ng-select-container + .ng-value-container + .ng-value { + background-color: var(--warm-bg-accent) !important; + color: var(--warm-text-primary) !important; +} + +.ng-select.ng-select-multiple + .ng-select-container + .ng-value-container + .ng-value + .ng-value-icon { + color: var(--warm-text-muted) !important; +} + +.ng-select.ng-select-multiple + .ng-select-container + .ng-value-container + .ng-value + .ng-value-icon:hover { + background-color: var(--warm-accent-primary) !important; + color: white !important; +} + +/* ───────────────────────────────────────────────────────────────────────────────── + NG-SELECT BOOTSTRAP-LIKE STYLES (commented out, preserved for reference) + ───────────────────────────────────────────────────────────────────────────────── */ /* https://github.com/ng-select/ng-select/issues/1955#issuecomment-1240459808 */ /*.form-floating { position: relative; @@ -225,14 +1335,14 @@ ul.no-bullets { top: 0; left: 0; width: 100%; - height: 100%; // allow textareas + height: 100%; padding: 1rem 0.75rem; overflow: hidden; text-align: start; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; - border: 1px solid transparent; // Required for aligning label's text with the input as it affects inner box model + border: 1px solid transparent; transform-origin: 0 0; transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; } @@ -253,28 +1363,26 @@ ul.no-bullets { z-index: 1001; } - &:focus .ng-select-container { - border-color: #86b7fe; + border-color: var(--warm-accent-primary); outline: 0; - box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%); + box-shadow: 0 0 0 0.25rem var(--warm-input-focus-ring); } &.is-valid .ng-select-container { - border-color: #198754 !important; + border-color: var(--warm-success) !important; } &.is-valid.ng-select-focused .ng-select-container { - box-shadow: 0 0 0 0.25rem rgb(25 135 84 / 25%); + box-shadow: 0 0 0 0.25rem rgba(129, 178, 154, 0.25); } &.is-invalid .ng-select-container { - border-color: #dc3545 !important; - + border-color: var(--warm-danger) !important; } &.is-invalid.ng-select-focused .ng-select-container { - box-shadow: 0 0 0 0.25rem rgb(220 53 69 / 25%); + box-shadow: 0 0 0 0.25rem rgba(217, 85, 80, 0.25); } } }*/
This is a test email.
В случае возникновения вопросов обращайтесь: