diff --git a/src/app/pages/legal-notice/legal-notice.css b/src/app/pages/legal-notice/legal-notice.css index f3938bf..ce41742 100644 --- a/src/app/pages/legal-notice/legal-notice.css +++ b/src/app/pages/legal-notice/legal-notice.css @@ -2,151 +2,58 @@ display: block; } -.legal-notice { - min-height: 100svh; - background-color: var(--color-foreground); - color: var(--color-canvas); +app-legal-page-layout { + --legal-content-min-height: 430px; + --legal-content-line-height: 1.2; } -.legal-notice__hero { - height: 271px; - background-color: var(--color-foreground); - background-image: url('/assets/legal-notice/legal-notice-header-bg.png'); - background-repeat: no-repeat; - background-position: center bottom; - background-size: 100% 100%; - color: var(--color-foreground); -} - -.legal-notice__hero-inner { - display: flex; - width: min(1440px, 100%); - height: 100%; - margin-inline: auto; - padding: 0 20px 95px; - align-items: flex-end; -} - -.legal-notice h1, -.legal-notice h2, -.legal-notice p { +h2, +p { margin: 0; } -.legal-notice h1 { - font-size: clamp(40px, 12.8vw, 48px); - font-weight: 700; - line-height: 1.2; -} - -.legal-notice__page { - width: min(1440px, 100%); - margin-inline: auto; -} - -.legal-notice__back { - display: inline-flex; - margin: 16px 20px 0; - align-items: center; - gap: 8px; - color: var(--color-canvas); - font-size: 18px; - font-weight: 700; - line-height: 1.2; - text-decoration: none; -} - -.legal-notice__back img { - width: 24px; - height: 24px; -} - -.legal-notice__back:is(:hover, :focus-visible, :active) { - color: var(--color-tertiary); -} - -.legal-notice__back:focus-visible, -.legal-notice__content a:focus-visible { - border-radius: 2px; - outline: 2px solid var(--color-primary); - outline-offset: 4px; -} - -.legal-notice__content { - display: flex; - width: min(1200px, calc(100% - 40px)); - min-height: 430px; - margin: 20px auto 0; - flex-direction: column; - gap: 32px; - font-size: 16px; - line-height: 1.2; -} - -.legal-notice__content section { +section { display: flex; flex-direction: column; gap: 16px; } -.legal-notice__content h2 { +h2 { color: var(--color-primary); font-size: 23px; font-weight: 700; line-height: 1.2; } -.legal-notice__content address { +address { display: flex; flex-direction: column; gap: 16px; font-style: normal; } -.legal-notice__content a { +a { color: var(--color-primary); + overflow-wrap: anywhere; text-underline-offset: 3px; } -.legal-notice__updated { - margin-top: auto; +a:focus-visible { + border-radius: 2px; + outline: 2px solid var(--color-primary); + outline-offset: 4px; } -.legal-notice app-footer { - display: block; - margin-top: 64px; +.legal-notice__updated { + margin-top: auto; } @media (min-width: 64rem) { - .legal-notice__hero { - height: 439px; - } - - .legal-notice__hero-inner { - padding: 0 clamp(32px, 8.333vw, 120px) 176px; + app-legal-page-layout { + --legal-content-min-height: 475px; } - .legal-notice h1 { - font-size: 64px; - line-height: 1; - } - - .legal-notice__back { - display: none; - } - - .legal-notice__content { - width: min(1200px, calc(100% - 64px)); - min-height: 475px; - margin-top: 80px; - gap: 40px; - } - - .legal-notice__content h2 { + h2 { font-size: 24px; } - - .legal-notice app-footer { - margin-top: 80px; - } } diff --git a/src/app/pages/legal-notice/legal-notice.html b/src/app/pages/legal-notice/legal-notice.html index 2fad30b..e5c2c51 100644 --- a/src/app/pages/legal-notice/legal-notice.html +++ b/src/app/pages/legal-notice/legal-notice.html @@ -1,53 +1,31 @@ -
- + +
+ -
+ +
+

Contact

+

+ Email: + contact@kamycoding.com +

+
- - -
+ + diff --git a/src/app/pages/legal-notice/legal-notice.spec.ts b/src/app/pages/legal-notice/legal-notice.spec.ts index 6182890..1645874 100644 --- a/src/app/pages/legal-notice/legal-notice.spec.ts +++ b/src/app/pages/legal-notice/legal-notice.spec.ts @@ -32,7 +32,7 @@ describe('LegalNotice', () => { it('reuses the shared light footer and provides a route back home', () => { const footer = fixture.nativeElement.querySelector('app-footer') as HTMLElement | null; const backLink = fixture.nativeElement.querySelector( - '.legal-notice__back', + '.legal-page__back', ) as HTMLAnchorElement | null; expect(footer?.querySelector('.site-footer--light')).not.toBeNull(); diff --git a/src/app/pages/legal-notice/legal-notice.ts b/src/app/pages/legal-notice/legal-notice.ts index df6ce61..8d4c9a6 100644 --- a/src/app/pages/legal-notice/legal-notice.ts +++ b/src/app/pages/legal-notice/legal-notice.ts @@ -1,11 +1,10 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { Footer } from '../../layout/footer/footer'; +import { LegalPageLayout } from '../../shared/legal-page-layout/legal-page-layout'; @Component({ selector: 'app-legal-notice', - imports: [RouterLink, Footer], + imports: [LegalPageLayout], templateUrl: './legal-notice.html', styleUrl: './legal-notice.css', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/pages/privacy-policy/privacy-policy.css b/src/app/pages/privacy-policy/privacy-policy.css index 8e28cc0..99bd5b0 100644 --- a/src/app/pages/privacy-policy/privacy-policy.css +++ b/src/app/pages/privacy-policy/privacy-policy.css @@ -2,148 +2,47 @@ display: block; } -.privacy-policy { - min-height: 100svh; - background-color: var(--color-foreground); - color: var(--color-canvas); -} - -.privacy-policy__hero { - height: 271px; - background-color: var(--color-foreground); - background-image: url('/assets/legal-notice/legal-notice-header-bg.png'); - background-repeat: no-repeat; - background-position: center bottom; - background-size: 100% 100%; - color: var(--color-foreground); -} - -.privacy-policy__hero-inner { - display: flex; - width: min(1440px, 100%); - height: 100%; - margin-inline: auto; - padding: 0 20px 95px; - align-items: flex-end; -} - -.privacy-policy h1, -.privacy-policy h2, -.privacy-policy p, -.privacy-policy ul { +h2, +p, +ul { margin: 0; } -.privacy-policy h1 { - font-size: clamp(40px, 12.8vw, 48px); - font-weight: 700; - line-height: 1.2; -} - -.privacy-policy__page { - width: min(1440px, 100%); - margin-inline: auto; -} - -.privacy-policy__back { - display: inline-flex; - margin: 16px 20px 0; - align-items: center; - gap: 8px; - color: var(--color-canvas); - font-size: 18px; - font-weight: 700; - line-height: 1.2; - text-decoration: none; -} - -.privacy-policy__back img { - width: 24px; - height: 24px; -} - -.privacy-policy__back:is(:hover, :focus-visible, :active) { - color: var(--color-tertiary); -} - -.privacy-policy__back:focus-visible, -.privacy-policy__content a:focus-visible { - border-radius: 2px; - outline: 2px solid var(--color-primary); - outline-offset: 4px; -} - -.privacy-policy__content { - display: flex; - width: min(1200px, calc(100% - 40px)); - margin: 20px auto 0; - flex-direction: column; - gap: 32px; - font-size: 16px; - line-height: 1.5; -} - -.privacy-policy__content section { +section { display: flex; flex-direction: column; gap: 16px; } -.privacy-policy__content h2 { +h2 { color: var(--color-primary); font-size: 23px; font-weight: 700; line-height: 1.2; } -.privacy-policy__content address { +address { font-style: normal; } -.privacy-policy__content ul { +ul { padding-inline-start: 24px; } -.privacy-policy__content a { +a { color: var(--color-primary); overflow-wrap: anywhere; text-underline-offset: 3px; } -.privacy-policy app-footer { - display: block; - margin-top: 64px; +a:focus-visible { + border-radius: 2px; + outline: 2px solid var(--color-primary); + outline-offset: 4px; } @media (min-width: 64rem) { - .privacy-policy__hero { - height: 439px; - } - - .privacy-policy__hero-inner { - padding: 0 clamp(32px, 8.333vw, 120px) 176px; - } - - .privacy-policy h1 { - font-size: 64px; - line-height: 1; - } - - .privacy-policy__back { - display: none; - } - - .privacy-policy__content { - width: min(1200px, calc(100% - 64px)); - margin-top: 80px; - gap: 40px; - } - - .privacy-policy__content h2 { + h2 { font-size: 24px; } - - .privacy-policy app-footer { - margin-top: 80px; - } } diff --git a/src/app/pages/privacy-policy/privacy-policy.html b/src/app/pages/privacy-policy/privacy-policy.html index a8ae172..54b4532 100644 --- a/src/app/pages/privacy-policy/privacy-policy.html +++ b/src/app/pages/privacy-policy/privacy-policy.html @@ -1,224 +1,201 @@ -
-
-
-

Privacy Policy

-
-
- -
- - - Back - - -
-
-

1. Controller

-

The controller responsible for data processing on this website is:

- -
- Kamyar Zamanfar
- KamyCoding
- Alte Poststraße 14
- 53840 Troisdorf
- Germany -
- -

- Email: - contact@kamycoding.com -

-
- -
-

2. General Information

-

The protection of your personal data is important to me.

-

- Personal data is any information relating to an identified or identifiable natural person. -

-

- This privacy policy explains what personal data is processed when you use this website, - for what purposes it is processed, and what rights you have under the General Data - Protection Regulation (GDPR). -

-
- -
-

3. Hosting and Server Log Files

-

This website and its backend are hosted using external hosting providers.

-

- When you access this website, technical information may automatically be processed by the - hosting infrastructure. This may include, in particular: -

-
    -
  • IP address
  • -
  • date and time of access
  • -
  • requested page or file
  • -
  • referrer URL
  • -
  • browser type and version
  • -
  • operating system
  • -
  • HTTP status code
  • -
-

- This processing is necessary to provide the website securely and reliably and to detect - technical problems or misuse. -

-

- The legal basis is Art. 6(1)(f) GDPR. My legitimate interest is the secure and technically - reliable operation of this website. -

-

- Hosting provider information will be added after the final production hosting setup has - been completed. -

-
- -
-

4. Contact Form

-

- If you contact me using the contact form, the information you enter will be processed in - order to handle and respond to your request. -

-

The contact form may process the following data:

-
    -
  • name
  • -
  • email address
  • -
  • message
  • -
  • information concerning acceptance of the privacy notice
  • -
-

The information is used to process your enquiry and communicate with you.

-

- Depending on the nature of your request, the legal basis is Art. 6(1)(b) GDPR where the - communication concerns pre-contractual measures, or Art. 6(1)(f) GDPR for other general - enquiries. -

-

- My legitimate interest is to receive and respond to enquiries submitted through this - website. -

-
- -
-

5. Email Delivery via Brevo

-

- Messages submitted through the contact form are technically transmitted using the email - delivery service Brevo. -

-

- In this context, data contained in your enquiry, such as your name, email address and - message, may be processed by Brevo for the purpose of delivering the email. -

-

Brevo acts as a service provider for the technical delivery of these messages.

-

- Personal data is transmitted only to the extent necessary for the delivery and processing - of your enquiry. -

-
- -
-

6. Email Hosting via Zoho Mail

-

- Emails sent to - contact@kamycoding.com - are hosted and processed using Zoho Mail. -

-

- Zoho may process email content, sender and recipient addresses, and technical metadata - necessary for providing the email service. -

-

The processing is necessary to receive, store and respond to communications.

-

- The legal basis is Art. 6(1)(b) GDPR where the communication concerns contractual or - pre-contractual matters and Art. 6(1)(f) GDPR for general communication. -

-
- -
-

7. Storage Period

-

- Personal data submitted through the contact form or by email is stored only for as long as - necessary to process and respond to the relevant enquiry. -

-

- Data may be retained for a longer period where this is necessary to comply with statutory - retention obligations or to establish, exercise or defend legal claims. -

-

- After the relevant purpose no longer applies and no statutory retention obligations - remain, the data will be deleted. -

-
- -
-

8. Cookies and Similar Technologies

-

- This website does not currently use non-essential cookies for analytics, advertising or - marketing purposes. -

-

- If non-essential cookies, analytics tools or similar technologies are introduced in the - future, this privacy policy and, where legally required, the website's consent mechanism - will be updated accordingly. -

-
- -
-

9. Your Rights

-

Under the GDPR, you may have the following rights regarding your personal data:

-
    -
  • right of access
  • -
  • right to rectification
  • -
  • right to erasure
  • -
  • right to restriction of processing
  • -
  • right to data portability
  • -
  • right to object to processing
  • -
  • right to withdraw consent where processing is based on consent
  • -
  • right to lodge a complaint with a competent data protection supervisory authority
  • -
-

- To exercise your rights, contact: - contact@kamycoding.com -

-
- -
-

10. Right to Object

-

- Where personal data is processed on the basis of Art. 6(1)(f) GDPR, you have the right to - object to such processing on grounds relating to your particular situation. -

-
- -
-

11. Security

-

- Appropriate technical and organisational measures are used to protect personal data - against loss, misuse, unauthorised access or disclosure. -

-

- Communication with this website is provided via encrypted HTTPS connections where - technically available. -

-
- -
-

12. Changes to this Privacy Policy

-

- This privacy policy may be updated if the website, the services used, or the applicable - legal requirements change. -

-

- Last updated: - -

-
-
- - -
-
+ +
+

1. Controller

+

The controller responsible for data processing on this website is:

+ +
+ Kamyar Zamanfar
+ KamyCoding
+ Alte Poststraße 14
+ 53840 Troisdorf
+ Germany +
+ +

+ Email: + contact@kamycoding.com +

+
+ +
+

2. General Information

+

The protection of your personal data is important to me.

+

+ Personal data is any information relating to an identified or identifiable natural person. +

+

+ This privacy policy explains what personal data is processed when you use this website, for + what purposes it is processed, and what rights you have under the General Data Protection + Regulation (GDPR). +

+
+ +
+

3. Hosting and Server Log Files

+

This website and its backend are hosted using external hosting providers.

+

+ When you access this website, technical information may automatically be processed by the + hosting infrastructure. This may include, in particular: +

+ +

+ This processing is necessary to provide the website securely and reliably and to detect + technical problems or misuse. +

+

+ The legal basis is Art. 6(1)(f) GDPR. My legitimate interest is the secure and technically + reliable operation of this website. +

+

+ Hosting provider information will be added after the final production hosting setup has been + completed. +

+
+ +
+

4. Contact Form

+

+ If you contact me using the contact form, the information you enter will be processed in order + to handle and respond to your request. +

+

The contact form may process the following data:

+ +

The information is used to process your enquiry and communicate with you.

+

+ Depending on the nature of your request, the legal basis is Art. 6(1)(b) GDPR where the + communication concerns pre-contractual measures, or Art. 6(1)(f) GDPR for other general + enquiries. +

+

+ My legitimate interest is to receive and respond to enquiries submitted through this website. +

+
+ +
+

5. Email Delivery via Brevo

+

+ Messages submitted through the contact form are technically transmitted using the email + delivery service Brevo. +

+

+ In this context, data contained in your enquiry, such as your name, email address and message, + may be processed by Brevo for the purpose of delivering the email. +

+

Brevo acts as a service provider for the technical delivery of these messages.

+

+ Personal data is transmitted only to the extent necessary for the delivery and processing of + your enquiry. +

+
+ +
+

6. Email Hosting via Zoho Mail

+

+ Emails sent to + contact@kamycoding.com + are hosted and processed using Zoho Mail. +

+

+ Zoho may process email content, sender and recipient addresses, and technical metadata + necessary for providing the email service. +

+

The processing is necessary to receive, store and respond to communications.

+

+ The legal basis is Art. 6(1)(b) GDPR where the communication concerns contractual or + pre-contractual matters and Art. 6(1)(f) GDPR for general communication. +

+
+ +
+

7. Storage Period

+

+ Personal data submitted through the contact form or by email is stored only for as long as + necessary to process and respond to the relevant enquiry. +

+

+ Data may be retained for a longer period where this is necessary to comply with statutory + retention obligations or to establish, exercise or defend legal claims. +

+

+ After the relevant purpose no longer applies and no statutory retention obligations remain, + the data will be deleted. +

+
+ +
+

8. Cookies and Similar Technologies

+

+ This website does not currently use non-essential cookies for analytics, advertising or + marketing purposes. +

+

+ If non-essential cookies, analytics tools or similar technologies are introduced in the + future, this privacy policy and, where legally required, the website's consent mechanism will + be updated accordingly. +

+
+ +
+

9. Your Rights

+

Under the GDPR, you may have the following rights regarding your personal data:

+ +

+ To exercise your rights, contact: + contact@kamycoding.com +

+
+ +
+

10. Right to Object

+

+ Where personal data is processed on the basis of Art. 6(1)(f) GDPR, you have the right to + object to such processing on grounds relating to your particular situation. +

+
+ +
+

11. Security

+

+ Appropriate technical and organisational measures are used to protect personal data against + loss, misuse, unauthorised access or disclosure. +

+

+ Communication with this website is provided via encrypted HTTPS connections where technically + available. +

+
+ +
+

12. Changes to this Privacy Policy

+

+ This privacy policy may be updated if the website, the services used, or the applicable legal + requirements change. +

+

+ Last updated: + +

+
+
diff --git a/src/app/pages/privacy-policy/privacy-policy.spec.ts b/src/app/pages/privacy-policy/privacy-policy.spec.ts index 7c14891..650f898 100644 --- a/src/app/pages/privacy-policy/privacy-policy.spec.ts +++ b/src/app/pages/privacy-policy/privacy-policy.spec.ts @@ -39,7 +39,7 @@ describe('PrivacyPolicy', () => { it('reuses the shared light footer and provides a route back home', () => { const footer = fixture.nativeElement.querySelector('app-footer') as HTMLElement | null; const backLink = fixture.nativeElement.querySelector( - '.privacy-policy__back', + '.legal-page__back', ) as HTMLAnchorElement | null; const privacyLink = footer?.querySelector( 'a[href="/privacy-policy"]', diff --git a/src/app/pages/privacy-policy/privacy-policy.ts b/src/app/pages/privacy-policy/privacy-policy.ts index 03103e4..fab6b19 100644 --- a/src/app/pages/privacy-policy/privacy-policy.ts +++ b/src/app/pages/privacy-policy/privacy-policy.ts @@ -1,11 +1,10 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { Footer } from '../../layout/footer/footer'; +import { LegalPageLayout } from '../../shared/legal-page-layout/legal-page-layout'; @Component({ selector: 'app-privacy-policy', - imports: [RouterLink, Footer], + imports: [LegalPageLayout], templateUrl: './privacy-policy.html', styleUrl: './privacy-policy.css', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/shared/legal-page-layout/legal-page-layout.css b/src/app/shared/legal-page-layout/legal-page-layout.css new file mode 100644 index 0000000..df7063a --- /dev/null +++ b/src/app/shared/legal-page-layout/legal-page-layout.css @@ -0,0 +1,112 @@ +:host { + display: block; +} + +.legal-page { + min-height: 100svh; + background-color: var(--color-foreground); + color: var(--color-canvas); +} + +.legal-page__hero { + height: 271px; + background-color: var(--color-foreground); + background-image: url('/assets/legal-notice/legal-notice-header-bg.png'); + background-repeat: no-repeat; + background-position: center bottom; + background-size: 100% 100%; + color: var(--color-foreground); +} + +.legal-page__hero-inner { + display: flex; + width: min(1440px, 100%); + height: 100%; + margin-inline: auto; + padding: 0 20px 95px; + align-items: flex-end; +} + +.legal-page h1 { + margin: 0; + font-size: clamp(40px, 12.8vw, 48px); + font-weight: 700; + line-height: 1.2; +} + +.legal-page__page { + width: min(1440px, 100%); + margin-inline: auto; +} + +.legal-page__back { + display: inline-flex; + margin: 16px 20px 0; + align-items: center; + gap: 8px; + color: var(--color-canvas); + font-size: 18px; + font-weight: 700; + line-height: 1.2; + text-decoration: none; +} + +.legal-page__back img { + width: 24px; + height: 24px; +} + +.legal-page__back:is(:hover, :focus-visible, :active) { + color: var(--color-tertiary); +} + +.legal-page__back:focus-visible { + border-radius: 2px; + outline: 2px solid var(--color-primary); + outline-offset: 4px; +} + +.legal-page__content { + display: flex; + width: min(1200px, calc(100% - 40px)); + min-height: var(--legal-content-min-height, auto); + margin: 20px auto 0; + flex-direction: column; + gap: 32px; + font-size: 16px; + line-height: var(--legal-content-line-height, 1.5); +} + +.legal-page app-footer { + display: block; + margin-top: 64px; +} + +@media (min-width: 64rem) { + .legal-page__hero { + height: 439px; + } + + .legal-page__hero-inner { + padding: 0 clamp(32px, 8.333vw, 120px) 176px; + } + + .legal-page h1 { + font-size: 64px; + line-height: 1; + } + + .legal-page__back { + display: none; + } + + .legal-page__content { + width: min(1200px, calc(100% - 64px)); + margin-top: 80px; + gap: 40px; + } + + .legal-page app-footer { + margin-top: 80px; + } +} diff --git a/src/app/shared/legal-page-layout/legal-page-layout.html b/src/app/shared/legal-page-layout/legal-page-layout.html new file mode 100644 index 0000000..e1e4b4b --- /dev/null +++ b/src/app/shared/legal-page-layout/legal-page-layout.html @@ -0,0 +1,25 @@ +
+ + + +
diff --git a/src/app/shared/legal-page-layout/legal-page-layout.spec.ts b/src/app/shared/legal-page-layout/legal-page-layout.spec.ts new file mode 100644 index 0000000..843ea5b --- /dev/null +++ b/src/app/shared/legal-page-layout/legal-page-layout.spec.ts @@ -0,0 +1,43 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; + +import { LegalPageLayout } from './legal-page-layout'; + +@Component({ + imports: [LegalPageLayout], + template: ` + +

Projected legal content

+
+ `, +}) +class TestHost {} + +describe('LegalPageLayout', () => { + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TestHost], + providers: [provideRouter([])], + }).compileComponents(); + + fixture = TestBed.createComponent(TestHost); + fixture.detectChanges(); + }); + + it('renders its signal-based title input', () => { + const heading = fixture.nativeElement.querySelector('h1') as HTMLHeadingElement | null; + + expect(heading?.textContent).toBe('Test Legal Page'); + }); + + it('renders projected legal content', () => { + const projectedContent = fixture.nativeElement.querySelector( + '[data-testid="projected-content"]', + ) as HTMLParagraphElement | null; + + expect(projectedContent?.textContent).toBe('Projected legal content'); + }); +}); diff --git a/src/app/shared/legal-page-layout/legal-page-layout.ts b/src/app/shared/legal-page-layout/legal-page-layout.ts new file mode 100644 index 0000000..1b8860d --- /dev/null +++ b/src/app/shared/legal-page-layout/legal-page-layout.ts @@ -0,0 +1,15 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { RouterLink } from '@angular/router'; + +import { Footer } from '../../layout/footer/footer'; + +@Component({ + selector: 'app-legal-page-layout', + imports: [RouterLink, Footer], + templateUrl: './legal-page-layout.html', + styleUrl: './legal-page-layout.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class LegalPageLayout { + readonly title = input.required(); +}