MetacaseForm
@@ -2,7 +2,8 @@
|
|||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"cli": {
|
"cli": {
|
||||||
"packageManager": "npm"
|
"packageManager": "npm",
|
||||||
|
"analytics": false
|
||||||
},
|
},
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
@@ -38,8 +39,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "anyComponentStyle",
|
"type": "anyComponentStyle",
|
||||||
"maximumWarning": "4kB",
|
"maximumWarning": "10kB",
|
||||||
"maximumError": "8kB"
|
"maximumError": "12kB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputHashing": "all"
|
"outputHashing": "all"
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
interface Course {
|
||||||
|
category: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
level: string;
|
||||||
|
duration: string;
|
||||||
|
badge: string;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-courses-page',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
template: `
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero__content">
|
||||||
|
<span class="eyebrow">Learning Hub</span>
|
||||||
|
<h1>As nossas Componentes</h1>
|
||||||
|
<p>
|
||||||
|
Conehcimento das componentes de TargetOne, atraves de um conjunto de formações interativas e práticas, projetadas para potencializar o dominio das nossas soluções.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<main class="courses-page">
|
||||||
|
<section class="courses-header">
|
||||||
|
<div>
|
||||||
|
<h2>Formações Dispiniveis</h2>
|
||||||
|
<p>Formações diponiveis de TargetOne disponiveis para fazer</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="courses-summary">
|
||||||
|
<span>{{ courses.length }} courses</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="courses-grid">
|
||||||
|
<article class="course-card" *ngFor="let course of courses">
|
||||||
|
<div class="course-card__image" [style.background-image]="'url(' + course.image + ')'">
|
||||||
|
<span class="badge">{{ course.badge }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="course-card__content">
|
||||||
|
<span class="category">{{ course.category }}</span>
|
||||||
|
<h3>{{ course.title }}</h3>
|
||||||
|
<p>{{ course.description }}</p>
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<span>{{ course.level }}</span>
|
||||||
|
<span>{{ course.duration }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button">Ver curso</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
`,
|
||||||
|
styles: [`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-height: 100vh;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, rgba(94, 96, 206, 0.20), transparent 28%),
|
||||||
|
radial-gradient(circle at top right, rgba(76, 201, 240, 0.18), transparent 30%),
|
||||||
|
linear-gradient(180deg, #0b1020 0%, #11182d 35%, #f5f7fb 35%, #f5f7fb 100%);
|
||||||
|
color: #101828;
|
||||||
|
font-family: Inter, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
padding: 72px 24px 56px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__content {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: clamp(2.2rem, 5vw, 4rem);
|
||||||
|
line-height: 1.05;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero p {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0;
|
||||||
|
color: rgba(255, 255, 255, 0.82);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-page {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-header h2 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-header p {
|
||||||
|
margin: 0;
|
||||||
|
color: #667085;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-summary {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e4e7ec;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
box-shadow: 0 10px 30px rgba(16, 24, 40, 0.06);
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #eaecf0;
|
||||||
|
box-shadow: 0 20px 45px rgba(16, 24, 40, 0.08);
|
||||||
|
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
box-shadow: 0 24px 55px rgba(16, 24, 40, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card__image {
|
||||||
|
position: relative;
|
||||||
|
min-height: 220px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card__image::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(17, 24, 39, 0.10) 0%, rgba(17, 24, 39, 0.45) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
left: 18px;
|
||||||
|
z-index: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
color: #111827;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card__content {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #4f46e5;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card h3 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #101828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card p {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
color: #667085;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta span {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f2f4f7;
|
||||||
|
color: #344054;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
filter: brightness(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.courses-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero {
|
||||||
|
padding-top: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.courses-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class CoursesPageComponent {
|
||||||
|
courses: Course[] = [
|
||||||
|
{
|
||||||
|
category: 'Automation Basics',
|
||||||
|
title: 'Introduction to Intelligent Automation',
|
||||||
|
description: 'Aprenda os fundamentos da automação, os principais conceitos e como identificar oportunidades de melhoria em processos.',
|
||||||
|
level: 'Beginner',
|
||||||
|
duration: '2h 30min',
|
||||||
|
badge: 'Popular',
|
||||||
|
image: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?auto=format&fit=crop&w=1200&q=80'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'Development',
|
||||||
|
title: 'Build Your First Automation Project',
|
||||||
|
description: 'Curso prático com abordagem hands-on para criar, testar e publicar o seu primeiro fluxo de automação passo a passo.',
|
||||||
|
level: 'Intermediate',
|
||||||
|
duration: '4h 10min',
|
||||||
|
badge: 'Hands-on',
|
||||||
|
image: 'https://images.unsplash.com/photo-1555066931-4365d14bab8c?auto=format&fit=crop&w=1200&q=80'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: 'AI + Processes',
|
||||||
|
title: 'AI Tools for Modern Operations',
|
||||||
|
description: 'Descubra como combinar automação e ferramentas de IA para acelerar tarefas repetitivas e melhorar a eficiência operacional.',
|
||||||
|
level: 'Advanced',
|
||||||
|
duration: '3h 45min',
|
||||||
|
badge: 'New',
|
||||||
|
image: 'https://images.unsplash.com/photo-1677442136019-21780ecad995?auto=format&fit=crop&w=1200&q=80'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="276.8213" x2="292.749" xlink:href="#a" y1="271.2646" y2="271.2646"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="219.251" x2="235.1787" xlink:href="#a" y1="271.2646" y2="271.2646"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><path d="m284.785 273.825c3.531 0 6.582-2.102 7.964-5.121h-15.928c1.383 3.02 4.433 5.121 7.964 5.121z" fill="url(#b)"></path><path d="m227.215 273.825c3.531 0 6.581-2.102 7.964-5.121h-15.928c1.382 3.02 4.432 5.121 7.964 5.121z" fill="url(#c)"></path><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm28.785 87.091c-8.833 0-16.02-7.186-16.02-16.02 0-.514.108-1.002.3-1.445.003-.007.005-.014.008-.021l10.945-28.753c-1.847-.919-3.562-2.236-5.243-3.53-5.008-3.848-8.701-6.68-15.143-1.718v58.902c9.218.988 16.697 5.326 17.051 5.533 1.729 1.019 2.307 3.244 1.288 4.974-.676 1.15-1.889 1.792-3.134 1.792-.627 0-1.261-.162-1.839-.502-.075-.045-8.213-4.732-16.999-4.732-8.852 0-16.919 4.685-17 4.732-1.729 1.017-3.957.439-4.973-1.29-1.017-1.729-.44-3.955 1.289-4.974.353-.207 7.833-4.545 17.051-5.533v-59.039c-6.335-4.779-10.005-1.96-14.968 1.854-1.345 1.034-2.71 2.083-4.147 2.932l6.231 15.506c.748 1.861-.155 3.977-2.016 4.725-.444.179-.903.264-1.354.264-1.44 0-2.802-.862-3.372-2.28l-4.774-11.878-7.937 20.848h19.57c2.006 0 3.633 1.627 3.633 3.633 0 8.834-7.186 16.02-16.019 16.02s-16.02-7.186-16.02-16.02c0-.013.002-.025.002-.037-.018-.475.057-.959.235-1.429l10.956-28.781c-3.336-1.791-5.61-5.311-5.61-9.354 0-2.006 1.626-3.633 3.633-3.633s3.633 1.626 3.633 3.633c0 1.837 1.49 3.334 3.326 3.345 1.384-.011 3.528-1.659 5.602-3.253 2.904-2.232 6.195-4.762 10.391-5.604 3.02-.606 6.001-.25 9.005 1.086v-6.215c0-2.006 1.626-3.633 3.633-3.633s3.633 1.626 3.633 3.633v6.295c3.062-1.401 6.101-1.783 9.18-1.165 4.195.842 7.486 3.372 10.391 5.604 2.073 1.594 4.218 3.242 5.602 3.253 1.837-.01 3.326-1.507 3.326-3.345 0-2.006 1.627-3.633 3.633-3.633s3.633 1.626 3.633 3.633c0 4.078-2.313 7.623-5.695 9.399l5.959 14.897c.745 1.863-.161 3.976-2.024 4.722-.442.177-.899.261-1.348.261-1.441 0-2.806-.865-3.374-2.285l-4.01-10.025-7.232 18.999h19.5c2.007 0 3.633 1.627 3.633 3.633-.001 8.833-7.188 16.019-16.021 16.019z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="223.4248" x2="272.5908" xlink:href="#a" y1="255.7578" y2="255.7578"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="236.1021" x2="258.7275" xlink:href="#a" y1="237.5254" y2="237.5254"></linearGradient><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm41.293 73.651c0 2.007-1.626 3.633-3.633 3.633s-3.633-1.626-3.633-3.633v-4.191h-11.514v19.841c0 3.174 2.582 5.757 5.756 5.757h.001c1.537 0 2.983-.598 4.071-1.686 1.087-1.087 1.686-2.533 1.686-4.071 0-2.007 1.627-3.633 3.633-3.633 2.007 0 3.633 1.626 3.633 3.633 0 3.479-1.354 6.749-3.814 9.209-2.439 2.439-5.677 3.792-9.124 3.813-.028.001-.057.001-.085.001l-55.087-.001c-7.181 0-13.023-5.842-13.023-13.022l.001-64.477c0-2.006 1.626-3.633 3.633-3.633h55.087c2.007 0 3.633 1.626 3.633 3.633v37.37h15.147c2.007 0 3.633 1.626 3.633 3.633v7.824z" fill="url(#b)"></path><path d="m271.247 222.457h-47.821l-.001 60.844c0 3.175 2.583 5.757 5.758 5.757l43.408.001c-.86-1.737-1.344-3.691-1.344-5.758zm-29.231 61.348h-9.546c-2.006 0-3.633-1.627-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.546c2.006 0 3.633 1.626 3.633 3.633-.001 2.006-1.627 3.633-3.633 3.633zm0-12.05h-9.546c-2.006 0-3.633-1.628-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.546c2.006 0 3.633 1.626 3.633 3.633-.001 2.005-1.627 3.633-3.633 3.633zm0-12.208h-9.546c-2.006 0-3.633-1.626-3.633-3.633 0-2.006 1.627-3.633 3.633-3.633h9.546c2.006 0 3.633 1.627 3.633 3.633-.001 2.007-1.627 3.633-3.633 3.633zm20.344 24.258h-9.547c-2.005 0-3.633-1.627-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.547c2.007 0 3.633 1.626 3.633 3.633 0 2.006-1.626 3.633-3.633 3.633zm0-12.05h-9.547c-2.005 0-3.633-1.628-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.547c2.007 0 3.633 1.626 3.633 3.633 0 2.005-1.626 3.633-3.633 3.633zm0-12.208h-9.547c-2.005 0-3.633-1.626-3.633-3.633 0-2.006 1.627-3.633 3.633-3.633h9.547c2.007 0 3.633 1.627 3.633 3.633 0 2.007-1.626 3.633-3.633 3.633zm3.633-15.84c0 2.007-1.626 3.633-3.633 3.633h-29.891c-2.006 0-3.633-1.626-3.633-3.633v-12.363c0-2.007 1.627-3.633 3.633-3.633h29.891c2.007 0 3.633 1.626 3.633 3.633z" fill="url(#c)"></path><path d="m236.102 234.977h22.625v5.097h-22.625z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="194" x2="318" y1="256" y2="256"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm-32.645 23.591 5.297-5.325c1.414-1.423 3.714-1.428 5.137-.013s1.429 3.714.014 5.137l-5.31 5.338c-.01.009-.021.02-.031.03-.599.588-.929 1.365-.929 2.188 0 .822.33 1.6.929 2.187.011.01.021.021.031.031l5.31 5.337c1.415 1.423 1.409 3.723-.014 5.138-.708.705-1.636 1.057-2.562 1.057-.933 0-1.866-.356-2.575-1.071l-5.297-5.324c-1.992-1.964-3.088-4.575-3.088-7.355 0-2.779 1.096-5.391 3.088-7.355zm65.116 76.818-5.297 5.324c-.709.714-1.642 1.071-2.575 1.071-.927 0-1.853-.352-2.562-1.058-1.422-1.414-1.428-3.714-.013-5.137l5.31-5.339c.011-.01.02-.02.03-.03.6-.589.93-1.365.93-2.188 0-.822-.33-1.6-.93-2.188-.011-.01-.02-.021-.03-.03l-5.31-5.338c-1.415-1.423-1.409-3.723.013-5.138 1.423-1.415 3.723-1.409 5.138.014l5.297 5.324c1.992 1.963 3.088 4.575 3.088 7.355-.001 2.783-1.097 5.394-3.089 7.358zm-7.983-26.055c-.369.118-.743.175-1.111.175-1.536 0-2.963-.981-3.459-2.522-.613-1.91.438-3.956 2.348-4.57 9.136-2.935 15.273-11.358 15.273-20.962 0-12.138-9.875-22.013-22.013-22.013s-22.013 9.875-22.013 22.013c0 1.102.083 2.202.245 3.286 3.872 1.298 7.386 3.384 10.349 6.068.198-.521.511-.984.91-1.358.645-.692 1.553-1.135 2.566-1.16.387-.113 1.325-.938 1.49-3.377h-.177c-2.007 0-3.633-1.626-3.633-3.633 0-1.768 1.264-3.24 2.938-3.564-.087-.841-.146-1.744-.146-2.715 0-4.519 3.689-8.196 8.225-8.196h4.311c2.007 0 3.633 1.627 3.633 3.633s-1.626 3.633-3.633 3.633h-4.311c-.547 0-.959.4-.959.931 0 .944.077 1.816.189 2.647h2.109c2.007 0 3.633 1.627 3.633 3.633 0 2.007-1.626 3.633-3.633 3.633h-1.267c-.052 1.324-.241 2.502-.533 3.551h6.336c2.006 0 3.633 1.627 3.633 3.633s-1.627 3.633-3.633 3.633h-13.703c3.337 4.755 5.298 10.542 5.298 16.779 0 4.126-.842 8.116-2.504 11.861-.812 1.834-2.959 2.66-4.793 1.848-1.834-.813-2.661-2.96-1.848-4.793 1.248-2.812 1.88-5.811 1.88-8.916 0-12.138-9.875-22.013-22.013-22.013s-22.013 9.875-22.013 22.013 9.875 22.013 22.013 22.013c3.593 0 7.158-.884 10.307-2.557 1.772-.941 3.971-.268 4.912 1.505.941 1.771.268 3.972-1.504 4.911-4.195 2.229-8.938 3.406-13.715 3.406-16.145 0-29.278-13.134-29.278-29.278s13.134-29.278 29.278-29.277c.616 0 1.228.021 1.834.059-.038-.61-.06-1.223-.06-1.834 0-16.145 13.134-29.278 29.278-29.278 16.145 0 29.278 13.134 29.278 29.278.003 12.766-8.161 23.97-20.314 27.874zm-45.597 16.924c-2.007 0-3.633-1.627-3.633-3.633 0-2.007 1.626-3.633 3.633-3.633h7.082c.554 0 .973-.396.973-.921v-1.398c0-.516-.419-.903-.973-.903h-2.322c-4.535 0-8.225-3.733-8.225-8.323v-.146c0-3.536 2.259-6.556 5.415-7.702v-.874c0-2.006 1.626-3.632 3.633-3.632 2.005 0 3.633 1.626 3.633 3.632v.388c1.938.075 3.489 1.667 3.489 3.625 0 2.007-1.627 3.633-3.633 3.633h-4.311c-.546 0-.958.4-.958.931v.146c0 .564.448 1.058.958 1.058h2.322c4.543 0 8.239 3.664 8.239 8.169v1.398c0 3.78-2.594 6.97-6.105 7.906v.485c0 2.006-1.626 3.633-3.633 3.633s-3.633-1.627-3.633-3.633v-.205h-1.951z" fill="url(#a)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><defs><linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#f59e0b" /><stop offset="100%" stop-color="#f43f5e" /></linearGradient></defs><circle cx="256" cy="256" r="240" fill="url(#grad)" /><line x1="120" y1="180" x2="380" y2="180" stroke="white" stroke-width="24" stroke-linecap="round" /><line x1="120" y1="256" x2="380" y2="256" stroke="white" stroke-width="24" stroke-linecap="round" /><line x1="120" y1="332" x2="380" y2="332" stroke="white" stroke-width="24" stroke-linecap="round" /><circle cx="200" cy="180" r="28" fill="none" stroke="white" stroke-width="16" /><circle cx="300" cy="256" r="28" fill="none" stroke="white" stroke-width="16" /><circle cx="240" cy="332" r="28" fill="none" stroke="white" stroke-width="16" /></svg>
|
||||||
|
After Width: | Height: | Size: 863 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="269.5049" x2="273.6309" xlink:href="#a" y1="269.2891" y2="269.2891"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="238.1802" x2="242.3052" xlink:href="#a" y1="242.7104" y2="242.7104"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><circle cx="271.567" cy="269.29" fill="url(#b)" r="2.063"></circle><circle cx="240.243" cy="242.71" fill="url(#c)" r="2.063"></circle><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm40.23 88.89c-1.114 1.668-3.371 2.115-5.038 1.001-1.668-1.115-2.117-3.371-1.001-5.039 4.566-6.831 6.98-14.733 6.98-22.852 0-22.702-18.47-41.172-41.172-41.172s-41.172 18.47-41.172 41.172 18.47 41.172 41.172 41.172c7.529 0 14.895-2.05 21.298-5.93 1.717-1.04 3.95-.49 4.99 1.225 1.039 1.717.49 3.951-1.226 4.991-7.539 4.565-16.206 6.979-25.062 6.979-26.709 0-48.438-21.729-48.438-48.438s21.729-48.438 48.438-48.438 48.438 21.729 48.438 48.438c.001 9.562-2.837 18.859-8.207 26.891zm-53.334-9.632 20.524-37.97c.952-1.765 3.157-2.424 4.923-1.469s2.422 3.159 1.469 4.923l-20.525 37.971c-.657 1.216-1.908 1.906-3.199 1.906-.583 0-1.174-.141-1.724-.438-1.765-.954-2.422-3.158-1.468-4.923zm-2.653-21.219c-5.145 0-9.329-4.185-9.329-9.328 0-5.145 4.185-9.329 9.329-9.329s9.328 4.186 9.328 9.329-4.185 9.328-9.328 9.328zm31.324 7.922c5.145 0 9.329 4.185 9.329 9.328 0 5.145-4.186 9.329-9.329 9.329s-9.328-4.185-9.328-9.329c0-5.143 4.185-9.328 9.328-9.328z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="222.457" x2="245.8652" xlink:href="#a" y1="274.5527" y2="274.5527"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="265.6494" x2="289.0586" xlink:href="#a" y1="256.2432" y2="256.2432"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><path d="m234.161 259.719c-6.264 0-10.206 1.631-11.428 2.627 1.223.997 5.166 2.628 11.428 2.628.903 0 1.806-.036 2.684-.106 2-.16 3.751 1.331 3.911 3.331.161 2-1.331 3.751-3.331 3.911-1.069.086-2.167.13-3.264.13-4.325 0-8.454-.675-11.704-1.904v3.795c.766.938 4.843 2.894 11.704 2.894.903 0 1.806-.035 2.683-.106 1.998-.159 3.752 1.331 3.912 3.331.161 2-1.331 3.751-3.331 3.912-1.069.085-2.167.129-3.264.129-4.325 0-8.454-.675-11.704-1.903v4.109c.77.938 4.847 2.892 11.704 2.892 6.858 0 10.934-1.954 11.705-2.892v-23.884c-.771-.939-4.847-2.894-11.705-2.894z" fill="url(#b)"></path><path d="m289.058 225.991c-.771-.937-4.847-2.891-11.704-2.891-5.945 0-9.799 1.468-11.221 2.47 1.421 1.002 5.274 2.471 11.221 2.471.903 0 1.807-.036 2.683-.106 2.001-.161 3.752 1.33 3.912 3.33.161 2.001-1.331 3.752-3.331 3.912-1.069.086-2.168.129-3.263.129-4.326 0-8.455-.674-11.705-1.903v4.265c.768.937 4.843 2.892 11.705 2.892.903 0 1.806-.035 2.683-.106 2-.159 3.751 1.331 3.911 3.331.161 2-1.331 3.751-3.33 3.912-1.07.085-2.168.128-3.264.128-4.326 0-8.454-.673-11.705-1.902v3.794c.768.937 4.843 2.893 11.705 2.893.903 0 1.806-.036 2.683-.106 2-.162 3.751 1.331 3.912 3.33.16 2-1.331 3.752-3.331 3.912-1.069.086-2.168.13-3.264.13-4.326 0-8.454-.675-11.705-1.903v4.107c.768.937 4.844 2.894 11.705 2.894.903 0 1.806-.036 2.683-.106 2-.16 3.751 1.331 3.912 3.331.16 2-1.331 3.751-3.331 3.911-1.069.086-2.168.13-3.264.13-4.325 0-8.454-.675-11.704-1.904v3.795c.767.938 4.843 2.894 11.704 2.894.903 0 1.807-.035 2.684-.106 2-.159 3.751 1.331 3.911 3.331s-1.331 3.751-3.331 3.912c-1.069.085-2.168.129-3.263.129-4.326 0-8.455-.675-11.705-1.903v4.109c.771.938 4.847 2.892 11.705 2.892 6.856 0 10.933-1.954 11.703-2.892h-.001z" fill="url(#c)"></path><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm-33.935 47.916 4.94-4.862c1.87-1.878 4.354-2.911 6.999-2.911s5.128 1.033 6.999 2.911l4.939 4.862c1.43 1.408 1.448 3.708.041 5.137-.711.723-1.65 1.084-2.589 1.084-.92 0-1.841-.347-2.549-1.043l-4.954-4.876c-.01-.011-.021-.02-.03-.03-.499-.502-1.158-.779-1.857-.779-.7 0-1.359.276-1.857.779-.011.01-.02.02-.03.03l-4.954 4.876c-1.43 1.407-3.73 1.39-5.138-.041-1.407-1.429-1.389-3.729.04-5.137zm31.066 44.844c0 1.88-.877 5.392-6.752 7.79-3.323 1.355-7.661 2.103-12.218 2.103-4.556 0-8.895-.747-12.217-2.103-5.875-2.398-6.752-5.91-6.752-7.79v-24.415c0-1.88.877-5.392 6.752-7.79 3.322-1.355 7.661-2.102 12.217-2.102 4.557 0 8.896.747 12.218 2.102 5.875 2.398 6.752 5.91 6.752 7.79zm43.193 0c0 1.88-.877 5.392-6.752 7.79-3.322 1.355-7.662 2.103-12.218 2.103-4.557 0-8.896-.747-12.218-2.103-5.875-2.398-6.752-5.91-6.752-7.79v-61.035c0-.051.001-.103.002-.156-.001-.053-.002-.106-.002-.157 0-.338.051-.664.137-.975.408-1.94 1.904-4.579 6.615-6.502 3.322-1.356 7.661-2.103 12.219-2.103 4.556 0 8.895.747 12.217 2.103 5.875 2.398 6.752 5.91 6.752 7.79z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,114 @@
|
|||||||
|
######## Como publicar angular work ############
|
||||||
|
|
||||||
|
|
||||||
|
npm install -> se necessário
|
||||||
|
ng serve -> se necessário
|
||||||
|
|
||||||
|
ng build -> gerar o dist
|
||||||
|
|
||||||
|
ssh teu_user@IP_DO_SERVIDOR -> comando para entrar no serviro (meter user@ip)
|
||||||
|
|
||||||
|
##Criar pasta do site no servidor eu usei um docker##
|
||||||
|
|
||||||
|
mkdir -p /docker/angular/metacaseform
|
||||||
|
|
||||||
|
### Copiar os ficheiros do PC para o servidor onde coloquei a pagina da metacase do Docker ###
|
||||||
|
|
||||||
|
scp -r dist/form_metacase/browser/* USERNAME@IP:/docker/angular/metacaseform/
|
||||||
|
|
||||||
|
#### criar o composse.yml e as configurações para o Nginx para criar o Docker ####
|
||||||
|
|
||||||
|
nano /docker/angular/metacaseform/nginx.conf
|
||||||
|
|
||||||
|
nano /docker/angular/metacaseform/docker-compose.yml
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico {
|
||||||
|
log_not_found off;
|
||||||
|
access_log off;
|
||||||
|
try_files $uri =204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
angular-metacase:
|
||||||
|
image: nginx:latest
|
||||||
|
container_name: angular-metacase
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8090:80"
|
||||||
|
volumes:
|
||||||
|
- /docker/angular/metacaseform/app:/usr/share/nginx/html:ro
|
||||||
|
- /docker/angular/metacaseform/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
|
||||||
|
|
||||||
|
##### arrancar o container #####
|
||||||
|
|
||||||
|
|
||||||
|
cd /docker/angular/metacaseform
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
|
||||||
|
#### IR ao Ngnix configurar o acesso http…. blabla e escolher uma porta livre no caso especifico escolhi a 8090 ####
|
||||||
|
|
||||||
|
Block Common Exploits ✓
|
||||||
|
Websockets Support ✓
|
||||||
|
|
||||||
|
|
||||||
|
Request a new certificate
|
||||||
|
Force SSL ✓
|
||||||
|
HTTP/2 Support ✓
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Fui ao meu provaider de domínio registar o metacasehub ####
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
https://www.ovh.com/manager
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2️⃣ Ir para a gestão do domínio
|
||||||
|
|
||||||
|
No menu da esquerda, Web Cloud → Domains
|
||||||
|
|
||||||
|
Selecionar o domínio:
|
||||||
|
|
||||||
|
Clica em DNS Zone
|
||||||
|
|
||||||
|
Criar ou editar o registo
|
||||||
|
|
||||||
|
Clica em Add an entry Escolher A CNAE:
|
||||||
|
|
||||||
|
Subdomain: metacasehub (NESTE cASO)
|
||||||
|
Target: IP_PUBLICO
|
||||||
|
TTL: Default
|
||||||
|
|
||||||
|
Exemplo:
|
||||||
|
|
||||||
|
metacasehub → 85.xxx.xxx.xxx
|
||||||
|
|
||||||
|
|
||||||
|
Saber o IP publico no meu caso eu não usei isto porque estou a usar o duck DNS mas pode ser feito por aqui:
|
||||||
|
|
||||||
|
👉 https://whatismyipaddress.com/
|
||||||
|
|
||||||
|
ou correr no terminal:
|
||||||
|
|
||||||
|
curl ifconfig.me
|
||||||
|
|
||||||
|
|
||||||
|
e deve estar
|
||||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 74 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="276.8213" x2="292.749" xlink:href="#a" y1="271.2646" y2="271.2646"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="219.251" x2="235.1787" xlink:href="#a" y1="271.2646" y2="271.2646"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><path d="m284.785 273.825c3.531 0 6.582-2.102 7.964-5.121h-15.928c1.383 3.02 4.433 5.121 7.964 5.121z" fill="url(#b)"></path><path d="m227.215 273.825c3.531 0 6.581-2.102 7.964-5.121h-15.928c1.382 3.02 4.432 5.121 7.964 5.121z" fill="url(#c)"></path><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm28.785 87.091c-8.833 0-16.02-7.186-16.02-16.02 0-.514.108-1.002.3-1.445.003-.007.005-.014.008-.021l10.945-28.753c-1.847-.919-3.562-2.236-5.243-3.53-5.008-3.848-8.701-6.68-15.143-1.718v58.902c9.218.988 16.697 5.326 17.051 5.533 1.729 1.019 2.307 3.244 1.288 4.974-.676 1.15-1.889 1.792-3.134 1.792-.627 0-1.261-.162-1.839-.502-.075-.045-8.213-4.732-16.999-4.732-8.852 0-16.919 4.685-17 4.732-1.729 1.017-3.957.439-4.973-1.29-1.017-1.729-.44-3.955 1.289-4.974.353-.207 7.833-4.545 17.051-5.533v-59.039c-6.335-4.779-10.005-1.96-14.968 1.854-1.345 1.034-2.71 2.083-4.147 2.932l6.231 15.506c.748 1.861-.155 3.977-2.016 4.725-.444.179-.903.264-1.354.264-1.44 0-2.802-.862-3.372-2.28l-4.774-11.878-7.937 20.848h19.57c2.006 0 3.633 1.627 3.633 3.633 0 8.834-7.186 16.02-16.019 16.02s-16.02-7.186-16.02-16.02c0-.013.002-.025.002-.037-.018-.475.057-.959.235-1.429l10.956-28.781c-3.336-1.791-5.61-5.311-5.61-9.354 0-2.006 1.626-3.633 3.633-3.633s3.633 1.626 3.633 3.633c0 1.837 1.49 3.334 3.326 3.345 1.384-.011 3.528-1.659 5.602-3.253 2.904-2.232 6.195-4.762 10.391-5.604 3.02-.606 6.001-.25 9.005 1.086v-6.215c0-2.006 1.626-3.633 3.633-3.633s3.633 1.626 3.633 3.633v6.295c3.062-1.401 6.101-1.783 9.18-1.165 4.195.842 7.486 3.372 10.391 5.604 2.073 1.594 4.218 3.242 5.602 3.253 1.837-.01 3.326-1.507 3.326-3.345 0-2.006 1.627-3.633 3.633-3.633s3.633 1.626 3.633 3.633c0 4.078-2.313 7.623-5.695 9.399l5.959 14.897c.745 1.863-.161 3.976-2.024 4.722-.442.177-.899.261-1.348.261-1.441 0-2.806-.865-3.374-2.285l-4.01-10.025-7.232 18.999h19.5c2.007 0 3.633 1.627 3.633 3.633-.001 8.833-7.188 16.019-16.021 16.019z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="223.4248" x2="272.5908" xlink:href="#a" y1="255.7578" y2="255.7578"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="236.1021" x2="258.7275" xlink:href="#a" y1="237.5254" y2="237.5254"></linearGradient><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm41.293 73.651c0 2.007-1.626 3.633-3.633 3.633s-3.633-1.626-3.633-3.633v-4.191h-11.514v19.841c0 3.174 2.582 5.757 5.756 5.757h.001c1.537 0 2.983-.598 4.071-1.686 1.087-1.087 1.686-2.533 1.686-4.071 0-2.007 1.627-3.633 3.633-3.633 2.007 0 3.633 1.626 3.633 3.633 0 3.479-1.354 6.749-3.814 9.209-2.439 2.439-5.677 3.792-9.124 3.813-.028.001-.057.001-.085.001l-55.087-.001c-7.181 0-13.023-5.842-13.023-13.022l.001-64.477c0-2.006 1.626-3.633 3.633-3.633h55.087c2.007 0 3.633 1.626 3.633 3.633v37.37h15.147c2.007 0 3.633 1.626 3.633 3.633v7.824z" fill="url(#b)"></path><path d="m271.247 222.457h-47.821l-.001 60.844c0 3.175 2.583 5.757 5.758 5.757l43.408.001c-.86-1.737-1.344-3.691-1.344-5.758zm-29.231 61.348h-9.546c-2.006 0-3.633-1.627-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.546c2.006 0 3.633 1.626 3.633 3.633-.001 2.006-1.627 3.633-3.633 3.633zm0-12.05h-9.546c-2.006 0-3.633-1.628-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.546c2.006 0 3.633 1.626 3.633 3.633-.001 2.005-1.627 3.633-3.633 3.633zm0-12.208h-9.546c-2.006 0-3.633-1.626-3.633-3.633 0-2.006 1.627-3.633 3.633-3.633h9.546c2.006 0 3.633 1.627 3.633 3.633-.001 2.007-1.627 3.633-3.633 3.633zm20.344 24.258h-9.547c-2.005 0-3.633-1.627-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.547c2.007 0 3.633 1.626 3.633 3.633 0 2.006-1.626 3.633-3.633 3.633zm0-12.05h-9.547c-2.005 0-3.633-1.628-3.633-3.633 0-2.007 1.627-3.633 3.633-3.633h9.547c2.007 0 3.633 1.626 3.633 3.633 0 2.005-1.626 3.633-3.633 3.633zm0-12.208h-9.547c-2.005 0-3.633-1.626-3.633-3.633 0-2.006 1.627-3.633 3.633-3.633h9.547c2.007 0 3.633 1.627 3.633 3.633 0 2.007-1.626 3.633-3.633 3.633zm3.633-15.84c0 2.007-1.626 3.633-3.633 3.633h-29.891c-2.006 0-3.633-1.626-3.633-3.633v-12.363c0-2.007 1.627-3.633 3.633-3.633h29.891c2.007 0 3.633 1.626 3.633 3.633z" fill="url(#c)"></path><path d="m236.102 234.977h22.625v5.097h-22.625z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="194" x2="318" y1="256" y2="256"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm-32.645 23.591 5.297-5.325c1.414-1.423 3.714-1.428 5.137-.013s1.429 3.714.014 5.137l-5.31 5.338c-.01.009-.021.02-.031.03-.599.588-.929 1.365-.929 2.188 0 .822.33 1.6.929 2.187.011.01.021.021.031.031l5.31 5.337c1.415 1.423 1.409 3.723-.014 5.138-.708.705-1.636 1.057-2.562 1.057-.933 0-1.866-.356-2.575-1.071l-5.297-5.324c-1.992-1.964-3.088-4.575-3.088-7.355 0-2.779 1.096-5.391 3.088-7.355zm65.116 76.818-5.297 5.324c-.709.714-1.642 1.071-2.575 1.071-.927 0-1.853-.352-2.562-1.058-1.422-1.414-1.428-3.714-.013-5.137l5.31-5.339c.011-.01.02-.02.03-.03.6-.589.93-1.365.93-2.188 0-.822-.33-1.6-.93-2.188-.011-.01-.02-.021-.03-.03l-5.31-5.338c-1.415-1.423-1.409-3.723.013-5.138 1.423-1.415 3.723-1.409 5.138.014l5.297 5.324c1.992 1.963 3.088 4.575 3.088 7.355-.001 2.783-1.097 5.394-3.089 7.358zm-7.983-26.055c-.369.118-.743.175-1.111.175-1.536 0-2.963-.981-3.459-2.522-.613-1.91.438-3.956 2.348-4.57 9.136-2.935 15.273-11.358 15.273-20.962 0-12.138-9.875-22.013-22.013-22.013s-22.013 9.875-22.013 22.013c0 1.102.083 2.202.245 3.286 3.872 1.298 7.386 3.384 10.349 6.068.198-.521.511-.984.91-1.358.645-.692 1.553-1.135 2.566-1.16.387-.113 1.325-.938 1.49-3.377h-.177c-2.007 0-3.633-1.626-3.633-3.633 0-1.768 1.264-3.24 2.938-3.564-.087-.841-.146-1.744-.146-2.715 0-4.519 3.689-8.196 8.225-8.196h4.311c2.007 0 3.633 1.627 3.633 3.633s-1.626 3.633-3.633 3.633h-4.311c-.547 0-.959.4-.959.931 0 .944.077 1.816.189 2.647h2.109c2.007 0 3.633 1.627 3.633 3.633 0 2.007-1.626 3.633-3.633 3.633h-1.267c-.052 1.324-.241 2.502-.533 3.551h6.336c2.006 0 3.633 1.627 3.633 3.633s-1.627 3.633-3.633 3.633h-13.703c3.337 4.755 5.298 10.542 5.298 16.779 0 4.126-.842 8.116-2.504 11.861-.812 1.834-2.959 2.66-4.793 1.848-1.834-.813-2.661-2.96-1.848-4.793 1.248-2.812 1.88-5.811 1.88-8.916 0-12.138-9.875-22.013-22.013-22.013s-22.013 9.875-22.013 22.013 9.875 22.013 22.013 22.013c3.593 0 7.158-.884 10.307-2.557 1.772-.941 3.971-.268 4.912 1.505.941 1.771.268 3.972-1.504 4.911-4.195 2.229-8.938 3.406-13.715 3.406-16.145 0-29.278-13.134-29.278-29.278s13.134-29.278 29.278-29.277c.616 0 1.228.021 1.834.059-.038-.61-.06-1.223-.06-1.834 0-16.145 13.134-29.278 29.278-29.278 16.145 0 29.278 13.134 29.278 29.278.003 12.766-8.161 23.97-20.314 27.874zm-45.597 16.924c-2.007 0-3.633-1.627-3.633-3.633 0-2.007 1.626-3.633 3.633-3.633h7.082c.554 0 .973-.396.973-.921v-1.398c0-.516-.419-.903-.973-.903h-2.322c-4.535 0-8.225-3.733-8.225-8.323v-.146c0-3.536 2.259-6.556 5.415-7.702v-.874c0-2.006 1.626-3.632 3.633-3.632 2.005 0 3.633 1.626 3.633 3.632v.388c1.938.075 3.489 1.667 3.489 3.625 0 2.007-1.627 3.633-3.633 3.633h-4.311c-.546 0-.958.4-.958.931v.146c0 .564.448 1.058.958 1.058h2.322c4.543 0 8.239 3.664 8.239 8.169v1.398c0 3.78-2.594 6.97-6.105 7.906v.485c0 2.006-1.626 3.633-3.633 3.633s-3.633-1.627-3.633-3.633v-.205h-1.951z" fill="url(#a)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="269.5049" x2="273.6309" xlink:href="#a" y1="269.2891" y2="269.2891"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="238.1802" x2="242.3052" xlink:href="#a" y1="242.7104" y2="242.7104"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><circle cx="271.567" cy="269.29" fill="url(#b)" r="2.063"></circle><circle cx="240.243" cy="242.71" fill="url(#c)" r="2.063"></circle><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm40.23 88.89c-1.114 1.668-3.371 2.115-5.038 1.001-1.668-1.115-2.117-3.371-1.001-5.039 4.566-6.831 6.98-14.733 6.98-22.852 0-22.702-18.47-41.172-41.172-41.172s-41.172 18.47-41.172 41.172 18.47 41.172 41.172 41.172c7.529 0 14.895-2.05 21.298-5.93 1.717-1.04 3.95-.49 4.99 1.225 1.039 1.717.49 3.951-1.226 4.991-7.539 4.565-16.206 6.979-25.062 6.979-26.709 0-48.438-21.729-48.438-48.438s21.729-48.438 48.438-48.438 48.438 21.729 48.438 48.438c.001 9.562-2.837 18.859-8.207 26.891zm-53.334-9.632 20.524-37.97c.952-1.765 3.157-2.424 4.923-1.469s2.422 3.159 1.469 4.923l-20.525 37.971c-.657 1.216-1.908 1.906-3.199 1.906-.583 0-1.174-.141-1.724-.438-1.765-.954-2.422-3.158-1.468-4.923zm-2.653-21.219c-5.145 0-9.329-4.185-9.329-9.328 0-5.145 4.185-9.329 9.329-9.329s9.328 4.186 9.328 9.329-4.185 9.328-9.328 9.328zm31.324 7.922c5.145 0 9.329 4.185 9.329 9.328 0 5.145-4.186 9.329-9.329 9.329s-9.328-4.185-9.328-9.329c0-5.143 4.185-9.328 9.328-9.328z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="124" viewBox="194 194 124 124" width="124"><linearGradient id="a"><stop offset="0" stop-color="#f18a34"></stop><stop offset=".5029" stop-color="#ed7441"></stop><stop offset="1" stop-color="#e5274c"></stop></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="222.457" x2="245.8652" xlink:href="#a" y1="274.5527" y2="274.5527"></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="265.6494" x2="289.0586" xlink:href="#a" y1="256.2432" y2="256.2432"></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="194" x2="318" xlink:href="#a" y1="256" y2="256"></linearGradient><path d="m234.161 259.719c-6.264 0-10.206 1.631-11.428 2.627 1.223.997 5.166 2.628 11.428 2.628.903 0 1.806-.036 2.684-.106 2-.16 3.751 1.331 3.911 3.331.161 2-1.331 3.751-3.331 3.911-1.069.086-2.167.13-3.264.13-4.325 0-8.454-.675-11.704-1.904v3.795c.766.938 4.843 2.894 11.704 2.894.903 0 1.806-.035 2.683-.106 1.998-.159 3.752 1.331 3.912 3.331.161 2-1.331 3.751-3.331 3.912-1.069.085-2.167.129-3.264.129-4.325 0-8.454-.675-11.704-1.903v4.109c.77.938 4.847 2.892 11.704 2.892 6.858 0 10.934-1.954 11.705-2.892v-23.884c-.771-.939-4.847-2.894-11.705-2.894z" fill="url(#b)"></path><path d="m289.058 225.991c-.771-.937-4.847-2.891-11.704-2.891-5.945 0-9.799 1.468-11.221 2.47 1.421 1.002 5.274 2.471 11.221 2.471.903 0 1.807-.036 2.683-.106 2.001-.161 3.752 1.33 3.912 3.33.161 2.001-1.331 3.752-3.331 3.912-1.069.086-2.168.129-3.263.129-4.326 0-8.455-.674-11.705-1.903v4.265c.768.937 4.843 2.892 11.705 2.892.903 0 1.806-.035 2.683-.106 2-.159 3.751 1.331 3.911 3.331.161 2-1.331 3.751-3.33 3.912-1.07.085-2.168.128-3.264.128-4.326 0-8.454-.673-11.705-1.902v3.794c.768.937 4.843 2.893 11.705 2.893.903 0 1.806-.036 2.683-.106 2-.162 3.751 1.331 3.912 3.33.16 2-1.331 3.752-3.331 3.912-1.069.086-2.168.13-3.264.13-4.326 0-8.454-.675-11.705-1.903v4.107c.768.937 4.844 2.894 11.705 2.894.903 0 1.806-.036 2.683-.106 2-.16 3.751 1.331 3.912 3.331.16 2-1.331 3.751-3.331 3.911-1.069.086-2.168.13-3.264.13-4.325 0-8.454-.675-11.704-1.904v3.795c.767.938 4.843 2.894 11.704 2.894.903 0 1.807-.035 2.684-.106 2-.159 3.751 1.331 3.911 3.331s-1.331 3.751-3.331 3.912c-1.069.085-2.168.129-3.263.129-4.326 0-8.455-.675-11.705-1.903v4.109c.771.938 4.847 2.892 11.705 2.892 6.856 0 10.933-1.954 11.703-2.892h-.001z" fill="url(#c)"></path><path d="m256 194c-34.242 0-62 27.758-62 62s27.758 62 62 62 62-27.758 62-62-27.758-62-62-62zm-33.935 47.916 4.94-4.862c1.87-1.878 4.354-2.911 6.999-2.911s5.128 1.033 6.999 2.911l4.939 4.862c1.43 1.408 1.448 3.708.041 5.137-.711.723-1.65 1.084-2.589 1.084-.92 0-1.841-.347-2.549-1.043l-4.954-4.876c-.01-.011-.021-.02-.03-.03-.499-.502-1.158-.779-1.857-.779-.7 0-1.359.276-1.857.779-.011.01-.02.02-.03.03l-4.954 4.876c-1.43 1.407-3.73 1.39-5.138-.041-1.407-1.429-1.389-3.729.04-5.137zm31.066 44.844c0 1.88-.877 5.392-6.752 7.79-3.323 1.355-7.661 2.103-12.218 2.103-4.556 0-8.895-.747-12.217-2.103-5.875-2.398-6.752-5.91-6.752-7.79v-24.415c0-1.88.877-5.392 6.752-7.79 3.322-1.355 7.661-2.102 12.217-2.102 4.557 0 8.896.747 12.218 2.102 5.875 2.398 6.752 5.91 6.752 7.79zm43.193 0c0 1.88-.877 5.392-6.752 7.79-3.322 1.355-7.662 2.103-12.218 2.103-4.557 0-8.896-.747-12.218-2.103-5.875-2.398-6.752-5.91-6.752-7.79v-61.035c0-.051.001-.103.002-.156-.001-.053-.002-.106-.002-.157 0-.338.051-.664.137-.975.408-1.94 1.904-4.579 6.615-6.502 3.322-1.356 7.661-2.103 12.219-2.103 4.556 0 8.895.747 12.217 2.103 5.875 2.398 6.752 5.91 6.752 7.79z" fill="url(#d)"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 490 KiB |
|
After Width: | Height: | Size: 385 KiB |
|
After Width: | Height: | Size: 459 KiB |
|
After Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 474 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -1,3 +1,23 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
import { Formacaometacase } from './formacaometacase/formacaometacase';
|
||||||
|
import { CourseDetailPageComponent } from './course-detail-page/course-detail-page';
|
||||||
|
import { CourseLearningPageComponent } from './course-learning-page/course-learning-page';
|
||||||
|
|
||||||
export const routes: Routes = [];
|
export const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: Formacaometacase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'cursos/:slug',
|
||||||
|
component: CourseDetailPageComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'cursos/:slug/percurso',
|
||||||
|
component: CourseLearningPageComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
redirectTo: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
import { App } from './app';
|
import { App } from './app';
|
||||||
|
import { routes } from './app.routes';
|
||||||
|
|
||||||
describe('App', () => {
|
describe('App', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [App],
|
imports: [App],
|
||||||
|
providers: [provideRouter(routes)]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -13,11 +16,4 @@ describe('App', () => {
|
|||||||
const app = fixture.componentInstance;
|
const app = fixture.componentInstance;
|
||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render title', async () => {
|
|
||||||
const fixture = TestBed.createComponent(App);
|
|
||||||
await fixture.whenStable();
|
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
|
||||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, form_metacase');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
imports: [RouterOutlet],
|
imports: [RouterOutlet],
|
||||||
templateUrl: './app.html',
|
template: `<router-outlet></router-outlet>`
|
||||||
styleUrl: './app.css'
|
|
||||||
})
|
})
|
||||||
export class App {
|
export class App {
|
||||||
protected readonly title = signal('form_metacase');
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,527 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-detail-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-intro {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 24%),
|
||||||
|
linear-gradient(180deg, #ffffff 0%, #f6f9fd 100%);
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-intro__inner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.55fr) minmax(320px, 420px);
|
||||||
|
gap: 34px;
|
||||||
|
max-width: 1240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 56px 24px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-intro__main {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-pills {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-pill--muted {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-intro h1 {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: clamp(2.4rem, 5vw, 4rem);
|
||||||
|
line-height: 1.04;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-image {
|
||||||
|
display: block;
|
||||||
|
margin: 30px auto;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-summary {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: 0 0 24px;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 1.06rem;
|
||||||
|
line-height: 1.75;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
max-width: 720px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-stat {
|
||||||
|
padding: 18px 18px 16px;
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-stat span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-stat strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-icons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-icons__item {
|
||||||
|
width: auto;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 28px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 18px 42px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card__media {
|
||||||
|
aspect-ratio: 16 / 10;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card__media img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card__body {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card__topline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card__topline span {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 7px 11px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card h2 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 1.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enroll-card p {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-tabs {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
gap: 28px;
|
||||||
|
max-width: 1240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
background: rgba(248, 250, 252, 0.94);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-tabs a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 62px;
|
||||||
|
color: #334155;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 800;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-tabs a:hover {
|
||||||
|
color: #0f172a;
|
||||||
|
border-bottom-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.55fr) minmax(280px, 0.7fr);
|
||||||
|
gap: 30px;
|
||||||
|
max-width: 1240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 28px 24px 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-panel,
|
||||||
|
.sidebar-card,
|
||||||
|
.course-not-found__card {
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 28px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 14px 34px rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-panel {
|
||||||
|
padding: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header--split {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header__eyebrow,
|
||||||
|
.sidebar-card__eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-count {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-lead {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
color: #475569;
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-facts {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-facts article,
|
||||||
|
.detail-box {
|
||||||
|
padding: 18px;
|
||||||
|
border: 1px solid #e5edf6;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: #fbfdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-facts span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-columns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-box h3,
|
||||||
|
.learning-card h3,
|
||||||
|
.module-card__content h3 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bullet-list,
|
||||||
|
.sidebar-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
color: #475569;
|
||||||
|
line-height: 1.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bullet-list li + li,
|
||||||
|
.sidebar-list li + li {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-card {
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e5edf6;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-card p,
|
||||||
|
.module-card__content p {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: start;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e5edf6;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card__index {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card__duration {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 8px 11px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-card {
|
||||||
|
position: sticky;
|
||||||
|
top: 92px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-list {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tags span {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 50px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #1d4ed8;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.96rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button + .button {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--secondary {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found__card {
|
||||||
|
max-width: 520px;
|
||||||
|
padding: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found__card h1 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found__card p {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.course-intro__inner,
|
||||||
|
.detail-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-card {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.course-intro__inner {
|
||||||
|
padding: 40px 20px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-tabs {
|
||||||
|
gap: 18px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-layout {
|
||||||
|
padding: 22px 20px 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-stats,
|
||||||
|
.detail-facts,
|
||||||
|
.detail-columns,
|
||||||
|
.learning-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-panel,
|
||||||
|
.sidebar-card,
|
||||||
|
.course-not-found__card {
|
||||||
|
border-radius: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-panel {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header--split,
|
||||||
|
.module-card {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card__duration {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<ng-container *ngIf="course() as course; else notFound">
|
||||||
|
<div class="course-detail-page">
|
||||||
|
<section class="course-intro">
|
||||||
|
<div class="course-intro__inner">
|
||||||
|
<div class="course-intro__main">
|
||||||
|
<a routerLink="/" class="back-link">Voltar ao menu das formações</a>
|
||||||
|
|
||||||
|
<h1>{{ course.title }}</h1>
|
||||||
|
<p class="intro-summary">{{ course.summary }}</p>
|
||||||
|
|
||||||
|
<section id="overview" class="detail-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<span class="panel-header__eyebrow">Informação geral da formação</span>
|
||||||
|
<p class="detail-lead">{{ course.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-facts">
|
||||||
|
<article>
|
||||||
|
<span>Idioma</span>
|
||||||
|
<strong>{{ course.language }}</strong>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<span>Formato</span>
|
||||||
|
<strong>{{ course.format }}</strong>
|
||||||
|
</article>
|
||||||
|
<article class="intro-stat">
|
||||||
|
<span>Duração</span>
|
||||||
|
<strong>{{ course.duration }}</strong>
|
||||||
|
</article>
|
||||||
|
<article class="intro-stat">
|
||||||
|
<span>Modú los</span>
|
||||||
|
<strong>{{ course.modules.length }}</strong>
|
||||||
|
</article>
|
||||||
|
<div class="detail-box">
|
||||||
|
<span>Pré-requisitos</span>
|
||||||
|
<ul class="bullet-list">
|
||||||
|
<li *ngFor="let item of course.prerequisites">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="detail-box">
|
||||||
|
<span>Público-Alvo</span>
|
||||||
|
<ul class="bullet-list">
|
||||||
|
<li *ngFor="let item of course.audience">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside class="enroll-card">
|
||||||
|
<div class="enroll-card__media">
|
||||||
|
<img
|
||||||
|
[src]="course.image"
|
||||||
|
[alt]="course.title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="enroll-card__body">
|
||||||
|
<h2>Começar esta formação</h2>
|
||||||
|
<p>{{ course.description }}</p>
|
||||||
|
<a [routerLink]="['/cursos', course.slug, 'percurso']" class="button">Iniciar formação</a>
|
||||||
|
<a routerLink="/" class="button button--secondary">Voltar ao menu das formações</a>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<main class="detail-layout">
|
||||||
|
|
||||||
|
<section class="detail-main">
|
||||||
|
<section id="structure" class="detail-panel">
|
||||||
|
<div class="panel-header panel-header--split">
|
||||||
|
<div>
|
||||||
|
<span class="panel-header__eyebrow">Estrutura da formação</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="panel-count">{{ course.modules.length }} lições</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="module-list">
|
||||||
|
<article
|
||||||
|
*ngFor="let module of course.modules; let index = index"
|
||||||
|
class="module-card"
|
||||||
|
>
|
||||||
|
<div class="module-card__index">{{ index + 1 }}</div>
|
||||||
|
|
||||||
|
<div class="module-card__content">
|
||||||
|
<h3>{{ module.title }}</h3>
|
||||||
|
<p>{{ module.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="module-card__duration">{{ module.duration }}</span>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<aside class="detail-sidebar">
|
||||||
|
<div class="sidebar-card">
|
||||||
|
<span class="sidebar-card__eyebrow">Competências da Formação</span>
|
||||||
|
<div class="learning-grid">
|
||||||
|
<article *ngFor="let item of course.objectives" class="topic-card">
|
||||||
|
<h3>{{ item }}</h3>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #notFound>
|
||||||
|
<div class="course-not-found">
|
||||||
|
<div class="course-not-found__card">
|
||||||
|
<h1>Formação não encontrada</h1>
|
||||||
|
<p>A formação selecionada não está disponível neste momento.</p>
|
||||||
|
<a routerLink="/" class="button">Voltar aos cursos</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, computed, inject } from '@angular/core';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
|
import { getCourseBySlug } from '../data/courses';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-course-detail-page',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, RouterLink],
|
||||||
|
templateUrl: './course-detail-page.html',
|
||||||
|
styleUrl: './course-detail-page.css'
|
||||||
|
})
|
||||||
|
export class CourseDetailPageComponent {
|
||||||
|
private readonly route = inject(ActivatedRoute);
|
||||||
|
private readonly paramMap = toSignal(this.route.paramMap);
|
||||||
|
|
||||||
|
readonly course = computed(() => {
|
||||||
|
const slug = this.paramMap()?.get('slug') ?? null;
|
||||||
|
return getCourseBySlug(slug);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,740 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-header {
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 28%),
|
||||||
|
linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-header__inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 24px;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 24px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-kicker {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(2.2rem, 4vw, 3.6rem);
|
||||||
|
line-height: 1.06;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-card {
|
||||||
|
min-width: 260px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 16px 30px rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-card__label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-card strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar span {
|
||||||
|
display: block;
|
||||||
|
width: 24%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: linear-gradient(90deg, #1d4ed8 0%, #3b82f6 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 320px minmax(0, 1fr);
|
||||||
|
gap: 28px;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 28px 24px 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-sidebar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-panel,
|
||||||
|
.content-panel,
|
||||||
|
.course-not-found__card {
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 28px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 14px 34px rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-panel {
|
||||||
|
position: sticky;
|
||||||
|
top: 24px;
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-panel__top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-panel__eyebrow {
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav__item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #e5edf6;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav__trigger {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav__item--active {
|
||||||
|
border-color: #bfdbfe;
|
||||||
|
background: #eff6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav__index {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav__content h3 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-nav__content p {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel {
|
||||||
|
padding: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 18px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel__eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel__header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-duration {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-intro {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
color: #475569;
|
||||||
|
line-height: 1.75;
|
||||||
|
white-space: pre-line;
|
||||||
|
text-align: justify
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin: 0 0 22px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%);
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border-color: #60a5fa;
|
||||||
|
box-shadow: 0 16px 32px rgba(37, 99, 235, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 56px;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__icon svg {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__eyebrow {
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__content strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__content span:last-child {
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #1d4ed8;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-topics {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
color: #475569;
|
||||||
|
line-height: 1.75;
|
||||||
|
text-align: justify;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-topics li {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-topics li::before {
|
||||||
|
content: "✓";
|
||||||
|
color: #475569;
|
||||||
|
margin: 0 0 18px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-icons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-icons__item {
|
||||||
|
width: auto;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-section + .lesson-section {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #e5edf6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-sequence {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-module {
|
||||||
|
scroll-margin-top: 28px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e5edf6;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-module__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-description {
|
||||||
|
margin: 0 0 14px;
|
||||||
|
color: #475569;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-section h3,
|
||||||
|
.question-card h3 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 1.16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-section p,
|
||||||
|
.question-note {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.75;
|
||||||
|
text-align: justify;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-section p + p {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-image {
|
||||||
|
display: block;
|
||||||
|
width: min(100%, 860px);
|
||||||
|
margin: 18px auto;
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.12);
|
||||||
|
cursor: zoom-in;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-image:hover {
|
||||||
|
transform: scale(1.01);
|
||||||
|
box-shadow: 0 22px 48px rgba(15, 23, 42, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 22px;
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: linear-gradient(180deg, #eff6ff 0%, #ffffff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__counter,
|
||||||
|
.image-lightbox__counter {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__stage {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #0f172a;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__slide {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #0f172a;
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__button {
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 0 18px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #1d4ed8;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-viewer__button:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
background: rgba(15, 23, 42, 0.82);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox__dialog {
|
||||||
|
position: relative;
|
||||||
|
max-width: min(1200px, 100%);
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox__toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
right: -12px;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox__action,
|
||||||
|
.image-lightbox__close {
|
||||||
|
min-width: 48px;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox__close {
|
||||||
|
min-width: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox__viewport {
|
||||||
|
overflow: auto;
|
||||||
|
max-width: min(1200px, calc(100vw - 48px));
|
||||||
|
max-height: calc(100vh - 48px);
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox__image {
|
||||||
|
display: block;
|
||||||
|
max-width: min(1200px, calc(100vw - 48px));
|
||||||
|
max-height: calc(100vh - 48px);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 28px 70px rgba(15, 23, 42, 0.35);
|
||||||
|
transition: transform 0.18s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-card {
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e5edf6;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-card__number {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-options {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 16px 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-option {
|
||||||
|
min-height: 50px;
|
||||||
|
border: 1px solid #dbe4f0;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-option:hover {
|
||||||
|
border-color: #93c5fd;
|
||||||
|
background: #f8fbff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-option:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-option.question-option--correct {
|
||||||
|
border-color: #16a34a;
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-option.question-option--incorrect {
|
||||||
|
border-color: #dc2626;
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 180px;
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 0 20px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #1d4ed8;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.96rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--secondary {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found__card {
|
||||||
|
max-width: 520px;
|
||||||
|
padding: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found__card h1 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-not-found__card p {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.learning-header__inner,
|
||||||
|
.learning-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-card,
|
||||||
|
.sidebar-panel {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.learning-header__inner,
|
||||||
|
.learning-layout {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learning-header__inner {
|
||||||
|
padding-top: 32px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel,
|
||||||
|
.sidebar-panel,
|
||||||
|
.course-not-found__card {
|
||||||
|
border-radius: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel__header,
|
||||||
|
.lesson-actions,
|
||||||
|
.presentation-viewer__header,
|
||||||
|
.presentation-viewer__actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-download-card__action {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
<ng-container *ngIf="course() as course; else notFound">
|
||||||
|
<div class="learning-page">
|
||||||
|
<header class="learning-header">
|
||||||
|
<div class="learning-header__inner">
|
||||||
|
<div>
|
||||||
|
<a [routerLink]="['/cursos', course.slug]" class="back-link">Voltar aos detalhes do curso</a>
|
||||||
|
<p class="course-kicker" style="display:none">{{ course.category }}</p>
|
||||||
|
<h1>{{ course.title }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="learning-layout">
|
||||||
|
<aside class="learning-sidebar">
|
||||||
|
<div class="sidebar-panel">
|
||||||
|
<div class="sidebar-panel__top">
|
||||||
|
<span class="sidebar-panel__eyebrow">Conteúdo da Formação</span>
|
||||||
|
<strong>{{ course.modules.length }} módulos</strong>
|
||||||
|
</div>
|
||||||
|
<div class="module-nav">
|
||||||
|
<article
|
||||||
|
*ngFor="let module of course.modules; let index = index"
|
||||||
|
class="module-nav__item"
|
||||||
|
[class.module-nav__item--active]="index === currentModuleIndex()"
|
||||||
|
>
|
||||||
|
<button type="button" class="module-nav__trigger" (click)="scrollToModule(index)">
|
||||||
|
<span class="module-nav__index">{{ index + 1 }}</span>
|
||||||
|
<div class="module-nav__content">
|
||||||
|
<h3>{{ module.title }}</h3>
|
||||||
|
<p>{{ module.duration }}</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<div class="progress-card">
|
||||||
|
<span class="progress-card__label">Progressão atual</span>
|
||||||
|
<strong>{{ progressLabel() }}</strong>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<span [style.width.%]="progressPercentage()"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="learning-main">
|
||||||
|
<div class="content-panel">
|
||||||
|
<div class="content-panel__header">
|
||||||
|
<div>
|
||||||
|
<span class="content-panel__eyebrow">Percurso da Formação</span>
|
||||||
|
<h2>Contextualização</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="content-intro">{{ course.summary }}</p>
|
||||||
|
|
||||||
|
<ul class="course-topics">
|
||||||
|
<li *ngFor="let topic of course.topics">{{ topic }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<section class="module-sequence">
|
||||||
|
<article
|
||||||
|
*ngFor="let item of modulesWithSections(); let index = index"
|
||||||
|
class="learning-module"
|
||||||
|
[attr.data-module-index]="index"
|
||||||
|
>
|
||||||
|
<div class="learning-module__header">
|
||||||
|
<span class="question-card__number">Módulo {{ index + 1 + " " + item.module.title }}</span>
|
||||||
|
<span class="lesson-duration">{{ item.module.duration }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="lesson-section" *ngIf="item.section as section">
|
||||||
|
<p *ngFor="let paragraph of section.paragraphs" [innerHTML]="paragraph"> </p>
|
||||||
|
|
||||||
|
<ul class="course-topics" *ngIf="section.bullets">
|
||||||
|
<li *ngFor="let bullet of section.bullets">
|
||||||
|
{{ bullet }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p *ngFor="let paragraph of section.paragraphs2" [innerHTML]="paragraph"> </p>
|
||||||
|
|
||||||
|
<img
|
||||||
|
*ngIf="section.imagem && section.imagem.trim() !== ''"
|
||||||
|
[src]="section.imagem"
|
||||||
|
class="lesson-image"
|
||||||
|
alt="Imagem da secao"
|
||||||
|
(click)="openZoomedImage(section.imagem)"
|
||||||
|
>
|
||||||
|
|
||||||
|
<p *ngFor="let paragraph of section.paragraphs3" [innerHTML]="paragraph"> </p>
|
||||||
|
|
||||||
|
<ul class="course-topics" *ngIf="section.bullets2">
|
||||||
|
<li *ngFor="let bullet of section.bullets2">
|
||||||
|
{{ bullet }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p *ngFor="let paragraph of section.paragraphs4" [innerHTML]="paragraph"> </p>
|
||||||
|
|
||||||
|
<ul class="course-topics" *ngIf="section.bullets3">
|
||||||
|
<li *ngFor="let bullet of section.bullets3">
|
||||||
|
{{ bullet }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p *ngFor="let paragraph of section.paragraphs5" [innerHTML]="paragraph"> </p>
|
||||||
|
|
||||||
|
<img
|
||||||
|
*ngIf="section.imagem2 && section.imagem2.trim() !== ''"
|
||||||
|
[src]="section.imagem2"
|
||||||
|
class="lesson-image"
|
||||||
|
alt="Imagem da secao"
|
||||||
|
(click)="openZoomedImage(section.imagem2)"
|
||||||
|
>
|
||||||
|
|
||||||
|
<p *ngFor="let paragraph of section.paragraphs6" [innerHTML]="paragraph"> </p>
|
||||||
|
|
||||||
|
<ul class="course-topics" *ngIf="section.bullets4">
|
||||||
|
<li *ngFor="let bullet of section.bullets4">
|
||||||
|
{{ bullet }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<img
|
||||||
|
*ngIf="section.imagem3 && section.imagem3.trim() !== ''"
|
||||||
|
[src]="section.imagem3"
|
||||||
|
class="lesson-image"
|
||||||
|
alt="Imagem da secao"
|
||||||
|
(click)="openZoomedImage(section.imagem3)"
|
||||||
|
>
|
||||||
|
|
||||||
|
<section class="presentation-viewer" *ngIf="section.presentationImages?.length">
|
||||||
|
<div class="presentation-viewer__header">
|
||||||
|
<div>
|
||||||
|
<span class="content-panel__eyebrow">Apresentação interativa</span>
|
||||||
|
</div>
|
||||||
|
<span class="presentation-viewer__counter">
|
||||||
|
Imagem {{ getPresentationImageIndex(index) + 1 }} / {{ section.presentationImages!.length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="presentation-viewer__stage">
|
||||||
|
<img
|
||||||
|
[src]="getPresentationImage(index, section.presentationImages!)"
|
||||||
|
class="presentation-viewer__slide"
|
||||||
|
alt="Imagem da apresentacao"
|
||||||
|
(click)="openZoomedImageGallery(section.presentationImages!, getPresentationImageIndex(index))"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="presentation-viewer__actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="presentation-viewer__button"
|
||||||
|
[disabled]="getPresentationImageIndex(index) === 0"
|
||||||
|
(click)="previousPresentationImage(index)"
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="presentation-viewer__button"
|
||||||
|
[disabled]="getPresentationImageIndex(index) >= section.presentationImages!.length - 1"
|
||||||
|
(click)="nextPresentationImage(index, section.presentationImages!)"
|
||||||
|
>
|
||||||
|
Seguinte
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
*ngIf="course.pdfPath"
|
||||||
|
class="resource-download-card"
|
||||||
|
[href]="course.pdfPath"
|
||||||
|
[attr.download]="course.pdfDownloadName ?? ''"
|
||||||
|
[attr.aria-label]="'Descarregar ' + (course.pdfTitle ?? 'documento PDF')"
|
||||||
|
>
|
||||||
|
<span class="resource-download-card__icon" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24" focusable="false">
|
||||||
|
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
|
||||||
|
<path d="M14 3v5h5" />
|
||||||
|
<path d="M12 11v6" />
|
||||||
|
<path d="m9.5 14.5 2.5 2.5 2.5-2.5" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="resource-download-card__content">
|
||||||
|
<strong>{{ course.pdfTitle ?? 'Documento PDF' }}</strong>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="resource-download-card__action">Download</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="content-panel">
|
||||||
|
<div class="content-panel__header">
|
||||||
|
<div>
|
||||||
|
<span class="content-panel__eyebrow">Validação de Conhecimentos</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="question-list">
|
||||||
|
<article *ngFor="let question of visibleQuestions(); let index = index" class="question-card">
|
||||||
|
<span class="question-card__number">Pergunta {{ index + 1 }}</span>
|
||||||
|
<h3>{{ question.prompt }}</h3>
|
||||||
|
|
||||||
|
<div class="question-options">
|
||||||
|
<button
|
||||||
|
*ngFor="let option of question.options; let optionIndex = index"
|
||||||
|
type="button"
|
||||||
|
class="question-option"
|
||||||
|
[class.question-option--correct]="isCorrectOption(index, optionIndex)"
|
||||||
|
[class.question-option--incorrect]="isWrongSelectedOption(index, optionIndex)"
|
||||||
|
[disabled]="isQuestionAnswered(index)"
|
||||||
|
(click)="selectAnswer(index, optionIndex)"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="question-note">{{ question.note }}</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lesson-actions">
|
||||||
|
<a [routerLink]="['/cursos', course.slug]" class="button button--secondary">Voltar ao curso</a>
|
||||||
|
<button type="button" class="button" (click)="scrollToModule(0)">Voltar ao primeiro modulo</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngIf="zoomedImageSrc() as zoomedImage" class="image-lightbox" (click)="closeZoomedImage()">
|
||||||
|
<div class="image-lightbox__dialog" (click)="$event.stopPropagation()">
|
||||||
|
<div class="image-lightbox__toolbar">
|
||||||
|
<button
|
||||||
|
*ngIf="zoomedImageSet().length > 1"
|
||||||
|
type="button"
|
||||||
|
class="image-lightbox__action"
|
||||||
|
[disabled]="zoomedImageIndex() === 0"
|
||||||
|
(click)="showPreviousZoomedImage()"
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</button>
|
||||||
|
<span *ngIf="zoomedImageSet().length > 1" class="image-lightbox__counter">
|
||||||
|
{{ zoomedImageIndex() + 1 }} / {{ zoomedImageSet().length }}
|
||||||
|
</span>
|
||||||
|
<button type="button" class="image-lightbox__action" (click)="zoomOutImage()">-</button>
|
||||||
|
<button type="button" class="image-lightbox__action" (click)="resetImageZoom()">100%</button>
|
||||||
|
<button type="button" class="image-lightbox__action" (click)="zoomInImage()">+</button>
|
||||||
|
<button
|
||||||
|
*ngIf="zoomedImageSet().length > 1"
|
||||||
|
type="button"
|
||||||
|
class="image-lightbox__action"
|
||||||
|
[disabled]="zoomedImageIndex() >= zoomedImageSet().length - 1"
|
||||||
|
(click)="showNextZoomedImage()"
|
||||||
|
>
|
||||||
|
Seguinte
|
||||||
|
</button>
|
||||||
|
<button type="button" class="image-lightbox__close" (click)="closeZoomedImage()">Fechar</button>
|
||||||
|
</div>
|
||||||
|
<div class="image-lightbox__viewport" (wheel)="handleImageZoomWheel($event, zoomedImageElement)">
|
||||||
|
<img
|
||||||
|
#zoomedImageElement
|
||||||
|
[src]="zoomedImage"
|
||||||
|
class="image-lightbox__image"
|
||||||
|
[style.transform]="'scale(' + zoomScale() + ')'"
|
||||||
|
[style.transform-origin]="zoomOrigin()"
|
||||||
|
alt="Imagem ampliada"
|
||||||
|
(click)="handleImageZoomClick($event, zoomedImageElement)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #notFound>
|
||||||
|
<div class="course-not-found">
|
||||||
|
<div class="course-not-found__card">
|
||||||
|
<h1>Percurso nao encontrado</h1>
|
||||||
|
<p>O percurso pedido nao esta disponivel neste momento.</p>
|
||||||
|
<a routerLink="/" class="button">Voltar aos cursos</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, HostListener, computed, effect, inject, signal } from '@angular/core';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
|
import { CourseQuestion, getCourseBySlug } from '../data/courses';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-course-learning-page',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, RouterLink],
|
||||||
|
templateUrl: './course-learning-page.html',
|
||||||
|
styleUrl: './course-learning-page.css'
|
||||||
|
})
|
||||||
|
export class CourseLearningPageComponent {
|
||||||
|
private static readonly QUESTIONS_PER_SESSION = 5;
|
||||||
|
private static readonly MIN_ZOOM_SCALE = 1;
|
||||||
|
private static readonly MAX_ZOOM_SCALE = 3;
|
||||||
|
private static readonly ZOOM_STEP = 0.25;
|
||||||
|
private readonly route = inject(ActivatedRoute);
|
||||||
|
private readonly paramMap = toSignal(this.route.paramMap);
|
||||||
|
private readonly selectedAnswers = signal<Record<number, number>>({});
|
||||||
|
private readonly presentationImageIndexes = signal<Record<number, number>>({});
|
||||||
|
readonly visibleQuestions = signal<CourseQuestion[]>([]);
|
||||||
|
readonly currentModuleIndex = signal(0);
|
||||||
|
readonly zoomedImageSet = signal<string[]>([]);
|
||||||
|
readonly zoomedImageIndex = signal(0);
|
||||||
|
readonly zoomedImageSrc = computed(() => this.zoomedImageSet()[this.zoomedImageIndex()] ?? null);
|
||||||
|
readonly zoomScale = signal(1);
|
||||||
|
readonly zoomOrigin = signal('50% 50%');
|
||||||
|
private readonly resetAnswersOnCourseChange = effect(() => {
|
||||||
|
const slug = this.paramMap()?.get('slug') ?? null;
|
||||||
|
const course = getCourseBySlug(slug);
|
||||||
|
this.selectedAnswers.set({});
|
||||||
|
this.presentationImageIndexes.set({});
|
||||||
|
this.currentModuleIndex.set(0);
|
||||||
|
this.zoomedImageSet.set([]);
|
||||||
|
this.zoomedImageIndex.set(0);
|
||||||
|
this.zoomScale.set(1);
|
||||||
|
this.zoomOrigin.set('50% 50%');
|
||||||
|
this.visibleQuestions.set(this.pickRandomQuestions(course?.questions ?? []));
|
||||||
|
setTimeout(() => this.updateCurrentModuleFromScroll(), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly course = computed(() => {
|
||||||
|
const slug = this.paramMap()?.get('slug') ?? null;
|
||||||
|
return getCourseBySlug(slug);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly modulesWithSections = computed(() => {
|
||||||
|
const course = this.course();
|
||||||
|
if (!course) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackSection = course.lessonSections[course.lessonSections.length - 1] ?? null;
|
||||||
|
return course.modules.map((module, index) => ({
|
||||||
|
module,
|
||||||
|
section: course.lessonSections[index] ?? fallbackSection
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly progressLabel = computed(() => {
|
||||||
|
const course = this.course();
|
||||||
|
if (!course) {
|
||||||
|
return '0% complete';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.currentModuleIndex() + 1} of ${course.modules.length} módulos`;
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly progressPercentage = computed(() => {
|
||||||
|
const course = this.course();
|
||||||
|
if (!course || course.modules.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((this.currentModuleIndex() + 1) / course.modules.length) * 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
scrollToModule(index: number): void {
|
||||||
|
const section = document.querySelector<HTMLElement>(`[data-module-index="${index}"]`);
|
||||||
|
if (!section) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:scroll')
|
||||||
|
onWindowScroll(): void {
|
||||||
|
this.updateCurrentModuleFromScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:keydown.escape')
|
||||||
|
onEscapeKey(): void {
|
||||||
|
this.closeZoomedImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:keydown.arrowleft')
|
||||||
|
onArrowLeftKey(): void {
|
||||||
|
this.showPreviousZoomedImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:keydown.arrowright')
|
||||||
|
onArrowRightKey(): void {
|
||||||
|
this.showNextZoomedImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCurrentModuleFromScroll(): void {
|
||||||
|
const sections = Array.from(document.querySelectorAll<HTMLElement>('[data-module-index]'));
|
||||||
|
if (sections.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollProbe = window.scrollY + window.innerHeight * 0.35;
|
||||||
|
let activeIndex = 0;
|
||||||
|
|
||||||
|
sections.forEach((section, index) => {
|
||||||
|
if (section.offsetTop <= scrollProbe) {
|
||||||
|
activeIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (activeIndex !== this.currentModuleIndex()) {
|
||||||
|
this.currentModuleIndex.set(activeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAnswer(questionIndex: number, optionIndex: number): void {
|
||||||
|
const currentAnswers = this.selectedAnswers();
|
||||||
|
if (currentAnswers[questionIndex] !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedAnswers.set({
|
||||||
|
...currentAnswers,
|
||||||
|
[questionIndex]: optionIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isQuestionAnswered(questionIndex: number): boolean {
|
||||||
|
return this.selectedAnswers()[questionIndex] !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
isCorrectOption(questionIndex: number, optionIndex: number): boolean {
|
||||||
|
const question = this.visibleQuestions()[questionIndex];
|
||||||
|
if (!question || !this.isQuestionAnswered(questionIndex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return question.correctOptionIndex === optionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
isWrongSelectedOption(questionIndex: number, optionIndex: number): boolean {
|
||||||
|
const selectedOption = this.selectedAnswers()[questionIndex];
|
||||||
|
if (selectedOption === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const question = this.visibleQuestions()[questionIndex];
|
||||||
|
if (!question) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const correctOption = question.correctOptionIndex;
|
||||||
|
return selectedOption === optionIndex && selectedOption !== correctOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
openZoomedImage(imageSrc: string): void {
|
||||||
|
this.openZoomedImageGallery([imageSrc], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
openZoomedImageGallery(imageSet: string[], imageIndex: number): void {
|
||||||
|
this.zoomedImageSet.set(imageSet);
|
||||||
|
this.zoomedImageIndex.set(imageIndex);
|
||||||
|
this.zoomScale.set(1);
|
||||||
|
this.zoomOrigin.set('50% 50%');
|
||||||
|
}
|
||||||
|
|
||||||
|
closeZoomedImage(): void {
|
||||||
|
this.zoomedImageSet.set([]);
|
||||||
|
this.zoomedImageIndex.set(0);
|
||||||
|
this.zoomScale.set(1);
|
||||||
|
this.zoomOrigin.set('50% 50%');
|
||||||
|
}
|
||||||
|
|
||||||
|
showPreviousZoomedImage(): void {
|
||||||
|
const imageSet = this.zoomedImageSet();
|
||||||
|
const imageIndex = this.zoomedImageIndex();
|
||||||
|
if (imageSet.length <= 1 || imageIndex === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.zoomedImageIndex.set(imageIndex - 1);
|
||||||
|
this.resetImageZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
showNextZoomedImage(): void {
|
||||||
|
const imageSet = this.zoomedImageSet();
|
||||||
|
const imageIndex = this.zoomedImageIndex();
|
||||||
|
if (imageSet.length <= 1 || imageIndex >= imageSet.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.zoomedImageIndex.set(imageIndex + 1);
|
||||||
|
this.resetImageZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPresentationImage(moduleIndex: number): void {
|
||||||
|
const currentImage = this.getPresentationImageIndex(moduleIndex);
|
||||||
|
if (currentImage === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.presentationImageIndexes.update((indexes) => ({
|
||||||
|
...indexes,
|
||||||
|
[moduleIndex]: currentImage - 1
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPresentationImage(moduleIndex: number, images: string[]): void {
|
||||||
|
const currentImage = this.getPresentationImageIndex(moduleIndex);
|
||||||
|
if (currentImage >= images.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.presentationImageIndexes.update((indexes) => ({
|
||||||
|
...indexes,
|
||||||
|
[moduleIndex]: currentImage + 1
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
getPresentationImage(moduleIndex: number, images: string[]): string {
|
||||||
|
return images[this.getPresentationImageIndex(moduleIndex)] ?? images[0] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
getPresentationImageIndex(moduleIndex: number): number {
|
||||||
|
return this.presentationImageIndexes()[moduleIndex] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomInImage(): void {
|
||||||
|
this.zoomScale.update((scale) => Math.min(scale + CourseLearningPageComponent.ZOOM_STEP, CourseLearningPageComponent.MAX_ZOOM_SCALE));
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOutImage(): void {
|
||||||
|
this.zoomScale.update((scale) => Math.max(scale - CourseLearningPageComponent.ZOOM_STEP, CourseLearningPageComponent.MIN_ZOOM_SCALE));
|
||||||
|
}
|
||||||
|
|
||||||
|
resetImageZoom(): void {
|
||||||
|
this.zoomScale.set(1);
|
||||||
|
this.zoomOrigin.set('50% 50%');
|
||||||
|
}
|
||||||
|
|
||||||
|
handleImageZoomWheel(event: WheelEvent, imageElement: HTMLImageElement): void {
|
||||||
|
event.preventDefault();
|
||||||
|
this.updateZoomOriginFromPointer(event, imageElement);
|
||||||
|
|
||||||
|
if (event.deltaY < 0) {
|
||||||
|
this.zoomInImage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.zoomOutImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleImageZoomClick(event: MouseEvent, imageElement: HTMLImageElement): void {
|
||||||
|
this.updateZoomOriginFromPointer(event, imageElement);
|
||||||
|
this.zoomInImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateZoomOriginFromPointer(event: MouseEvent | WheelEvent, imageElement: HTMLImageElement): void {
|
||||||
|
const bounds = imageElement.getBoundingClientRect();
|
||||||
|
if (bounds.width === 0 || bounds.height === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeX = ((event.clientX - bounds.left) / bounds.width) * 100;
|
||||||
|
const relativeY = ((event.clientY - bounds.top) / bounds.height) * 100;
|
||||||
|
const clampedX = Math.min(Math.max(relativeX, 0), 100);
|
||||||
|
const clampedY = Math.min(Math.max(relativeY, 0), 100);
|
||||||
|
|
||||||
|
this.zoomOrigin.set(`${clampedX}% ${clampedY}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private pickRandomQuestions(questions: CourseQuestion[]): CourseQuestion[] {
|
||||||
|
const shuffledQuestions = [...questions];
|
||||||
|
|
||||||
|
for (let index = shuffledQuestions.length - 1; index > 0; index -= 1) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * (index + 1));
|
||||||
|
[shuffledQuestions[index], shuffledQuestions[randomIndex]] = [shuffledQuestions[randomIndex], shuffledQuestions[index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return shuffledQuestions.slice(0, CourseLearningPageComponent.QUESTIONS_PER_SESSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
.hero-logo-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
z-index: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: 1px solid rgba(191, 219, 254, 0.55);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(239, 246, 255, 0.88);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
width: clamp(120px, 16vw, 190px);
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card__badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
max-width: calc(100% - 32px);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card__badge-image {
|
||||||
|
width: auto;
|
||||||
|
max-width: 88px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 24px;
|
||||||
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-logo-badge {
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<div class="min-h-screen bg-slate-50 text-slate-900">
|
||||||
|
<section class="relative overflow-hidden bg-slate-950">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-r from-indigo-700/20 via-cyan-500/10 to-purple-700/20"></div>
|
||||||
|
<div class="hero-logo-badge">
|
||||||
|
<a href="https://www.metacase.pt" target="_blank" rel="noopener">
|
||||||
|
<img
|
||||||
|
src="/logo-metacase-cor.svg"
|
||||||
|
alt="Metacase"
|
||||||
|
class="hero-logo"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative mx-auto max-w-7xl px-6 py-20 lg:px-8">
|
||||||
|
<div class="max-w-3xl">
|
||||||
|
<span class="inline-flex rounded-full border border-white/20 bg-white/10 px-4 py-1 text-sm font-medium text-white">
|
||||||
|
Learning Hub
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<h1 class="mt-6 text-4xl font-bold tracking-tight text-white sm:text-5xl lg:text-6xl">
|
||||||
|
Formações de TargetOne
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="mt-6 text-lg leading-8 text-slate-300">
|
||||||
|
Formações práticas e atualizadas para a utilização da plataforma TargetOne <br>Aprendizagem orientada para a ferramenta com formações teóricas e práticas
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mx-auto max-w-7xl px-6 py-10 lg:px-8">
|
||||||
|
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-3xl font-bold tracking-tight text-slate-900">Formações disponíveis</h2>
|
||||||
|
<p class="mt-2 text-slate-600">
|
||||||
|
Atualmente encontram-se disponíveis {{ courses.length }} {{ courses.length === 1 ? 'formação' : 'formações' }} em destaque.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inline-flex w-fit items-center rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm">
|
||||||
|
<span class="text-sm font-semibold text-slate-700">
|
||||||
|
{{ courses.length }} {{ courses.length === 1 ? 'curso' : 'cursos' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mx-auto max-w-7xl px-6 pb-16 lg:px-8">
|
||||||
|
<div class="grid gap-8 md:grid-cols-2 xl:grid-cols-3">
|
||||||
|
<article
|
||||||
|
*ngFor="let course of courses"
|
||||||
|
class="group overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm transition duration-300 hover:-translate-y-1 hover:shadow-xl"
|
||||||
|
>
|
||||||
|
<div class="relative h-56 overflow-hidden">
|
||||||
|
<img
|
||||||
|
[src]="course.image"
|
||||||
|
[alt]="course.title"
|
||||||
|
class="h-full w-full object-cover transition duration-500 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent"></div>
|
||||||
|
|
||||||
|
<div class="course-card__badge">
|
||||||
|
<img
|
||||||
|
*ngFor="let icon of course.categoryIcons"
|
||||||
|
[alt]="course.category"
|
||||||
|
class="course-card__badge-image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="mt-2 text-xl font-bold text-slate-900">
|
||||||
|
{{ course.title }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p class="mt-3 text-sm leading-6 text-slate-600">
|
||||||
|
{{ course.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-wrap gap-2">
|
||||||
|
|
||||||
|
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-700">
|
||||||
|
{{ course.duration }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
[routerLink]="['/cursos', course.slug]"
|
||||||
|
class="mt-6 block w-full rounded-2xl bg-gradient-to-r from-indigo-600 to-purple-600 px-4 py-3 text-center text-sm font-semibold text-white transition hover:opacity-95"
|
||||||
|
>
|
||||||
|
Entrar
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
|
import { Formacaometacase } from './formacaometacase';
|
||||||
|
|
||||||
|
describe('Formacaometacase', () => {
|
||||||
|
let component: Formacaometacase;
|
||||||
|
let fixture: ComponentFixture<Formacaometacase>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Formacaometacase],
|
||||||
|
providers: [provideRouter([])]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Formacaometacase);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { COURSES, type Course } from '../data/courses';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-formacaometacase',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, RouterLink],
|
||||||
|
templateUrl: './formacaometacase.html',
|
||||||
|
styleUrl: './formacaometacase.css'
|
||||||
|
})
|
||||||
|
export class Formacaometacase {
|
||||||
|
readonly courses: Course[] = COURSES;
|
||||||
|
}
|
||||||