featmigrate from Angular to Astro static site

BREAKING CHANGE: Complete rewrite from Angular SPA to Astro

- Replace Angular 18 with Astro 5.16.7 + Tailwind CSS
- Convert all Angular components to Astro components
- Add content collections for blog with markdown support
- Setup S3 deployment with CloudFront invalidation
- Add RSS feed and sitemap generation
- Configure Prettier and Biome for code formatting
- Switch from npm to pnpm
- Remove Amplify backend (now fully static)
- Improve SEO and performance with static generation
This commit is contained in:
Lorenzo Iovino 2026-01-08 16:46:17 +01:00
parent 7be49ba73a
commit 7cf2e858a2
192 changed files with 7829 additions and 21756 deletions

View file

@ -0,0 +1,25 @@
{
"$ref": "#/definitions/bio",
"definitions": {
"bio": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"description"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1,76 @@
{
"$ref": "#/definitions/blog",
"definitions": {
"blog": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"pubDate": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "string",
"format": "date"
},
{
"type": "integer",
"format": "unix-time"
}
]
},
"updatedDate": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "string",
"format": "date"
},
{
"type": "integer",
"format": "unix-time"
}
]
},
"heroImage": {
"type": "string"
},
"author": {
"type": "string",
"default": "Lorenzo Iovino"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"draft": {
"type": "boolean",
"default": false
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"description",
"pubDate"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View file

@ -0,0 +1 @@
export default new Map();

View file

@ -0,0 +1 @@
export default new Map();

219
.astro/content.d.ts vendored Normal file
View file

@ -0,0 +1,219 @@
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export type ReferenceDataEntry<
C extends CollectionKey,
E extends keyof DataEntryMap[C] = string,
> = {
collection: C;
id: E;
};
export type ReferenceContentEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}) = string,
> = {
collection: C;
slug: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter?: LiveLoaderCollectionFilterType<C>,
): Promise<
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
entry: ReferenceContentEntry<C, E>,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
entry: ReferenceDataEntry<C, E>,
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter: string | LiveLoaderEntryFilterType<C>,
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
collection: C,
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type DataEntryMap = {
"bio": Record<string, {
id: string;
render(): Render[".md"];
slug: string;
body: string;
collection: "bio";
data: InferEntrySchema<"bio">;
rendered?: RenderedContent;
filePath?: string;
}>;
"blog": Record<string, {
id: string;
render(): Render[".md"];
slug: string;
body: string;
collection: "blog";
data: InferEntrySchema<"blog">;
rendered?: RenderedContent;
filePath?: string;
}>;
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
infer TCollectionFilter,
infer TError
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
LiveContentConfig['collections'][C]['schema'] extends undefined
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
: import('astro/zod').infer<
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
>;
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
LiveContentConfig['collections'][C]['loader']
>;
export type ContentConfig = typeof import("../src/content/config.js");
export type LiveContentConfig = never;
}

1
.astro/data-store.json Normal file

File diff suppressed because one or more lines are too long

5
.astro/settings.json Normal file
View file

@ -0,0 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1767866746327
}
}

2
.astro/types.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

4
.biomeignore Normal file
View file

@ -0,0 +1,4 @@
.astro
dist
node_modules
*.css

View file

@ -1,16 +0,0 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

9
.env
View file

@ -1 +1,8 @@
PERFORMANCE_PROFILER=true # OpenWeatherMap API Key
# Get your free API key at: https://openweathermap.org/api
# Free tier includes 1,000 API calls per day
PUBLIC_OPENWEATHER_API_KEY=0938949580094b5d165c2e7cd23b7cf2
# Weather Location Configuration
PUBLIC_WEATHER_CITY=Rome
PUBLIC_WEATHER_COUNTRY_CODE=IT

View file

@ -1 +0,0 @@
PERFORMANCE_PROFILER=false

View file

@ -1,32 +1,65 @@
name: Upload to S3 name: Deploy to S3
on: on:
push: push:
branches: [main] branches: [main]
jobs: jobs:
upload: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest]
node-version: [20.x]
steps: steps:
- uses: actions/checkout@v2 - name: Checkout code
- run: npm install uses: actions/checkout@v4
- run: npm run build
- run: npm run generate-sitemap - name: Setup Node.js
- uses: aws-actions/configure-aws-credentials@v1 uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm type-check
- name: Build
run: pnpm build
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with: with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-south-1 aws-region: eu-south-1
- run: aws s3 sync ./dist/loreiov.com/browser/ s3://${{ secrets.AWS_BUCKET }} --delete
- uses: chetan/invalidate-cloudfront-action@v2 - name: Sync to S3
run: aws s3 sync ./dist s3://${{ secrets.AWS_BUCKET }} --delete --cache-control "public, max-age=31536000, immutable"
- name: Invalidate CloudFront cache
uses: chetan/invalidate-cloudfront-action@v2
env: env:
DISTRIBUTION: ${{ secrets.DISTRIBUTION }} DISTRIBUTION: ${{ secrets.DISTRIBUTION }}
PATHS: "/index.html" PATHS: "/*"
AWS_REGION: "eu-south-1" AWS_REGION: "eu-south-1"
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

60
.gitignore vendored
View file

@ -1,46 +1,32 @@
# See http://help.github.com/ignore-files/ for more about ignoring files. # build output
dist/
.output/
# Compiled output # dependencies
/dist node_modules/
/tmp
/out-tsc
/bazel-out
# Node # logs
/node_modules npm-debug.log*
npm-debug.log yarn-debug.log*
yarn-error.log yarn-error.log*
pnpm-debug.log*
# IDEs and editors # environment variables
.idea/ .env
.project .env.production
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code # macOS-specific files
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store .DS_Store
Thumbs.db
# jetbrains setting folders
.idea/
# vscode setting folders
.vscode/
# amplify # amplify
.amplify .amplify
amplifyconfiguration* amplifyconfiguration*
# misc
Thumbs.db

37
.prettierignore Normal file
View file

@ -0,0 +1,37 @@
# Dependencies
node_modules/
pnpm-lock.yaml
# Build output
dist/
.output/
.astro/
# Environment files
.env
.env.*
# System files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
# Git
.git/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Package files
package-lock.json
yarn.lock
# Cache
.cache/

19
.prettierrc.json Normal file
View file

@ -0,0 +1,19 @@
{
"plugins": ["prettier-plugin-astro"],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
],
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"useTabs": true,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always"
}

View file

@ -1,4 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
.vscode/launch.json vendored
View file

@ -1,20 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
.vscode/tasks.json vendored
View file

@ -1,42 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

51
.zed/settings.json Normal file
View file

@ -0,0 +1,51 @@
{
"languages": {
"Astro": {
"enable_language_server": true,
"formatter": "language_server",
"format_on_save": "on",
"tab_size": 2,
"hard_tabs": false
},
"TypeScript": {
"tab_size": 2,
"format_on_save": "on"
},
"TSX": {
"tab_size": 2,
"format_on_save": "on"
},
"CSS": {
"tab_size": 2,
"format_on_save": "on"
}
},
"lsp": {
"astro": {
"settings": {
"astro": {
"format": {
"enable": true
}
},
"typescript": {
"preferences": {
"importModuleSpecifier": "relative"
}
}
}
}
},
"formatter": {
"language_server": {
"name": "prettier"
}
},
"file_types": {
"Astro": ["astro"]
},
"tab_size": 2,
"format_on_save": "on",
"remove_trailing_whitespace_on_save": true,
"ensure_final_newline_on_save": true
}

View file

@ -1,27 +1,20 @@
# LoreiovCom # lorenzoiovino.com
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.8. My personal website
## Development server This project is built with [Astro](https://astro.build/) and [TailwindCSS](https://tailwindcss.com/).
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. ## Tech Stack
## Code scaffolding - **Framework**: [Astro](https://astro.build/) - Static site generator
- **Styling**: [TailwindCSS](https://tailwindcss.com/) - Utility-first CSS framework
- **Package Manager**: [pnpm](https://pnpm.io/)
- **Language**: TypeScript
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. ## 🌐 Deployment
## Build The site is configured to be deployed as a static site. The build output is in the `dist/` directory.
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. ## Author
## Running unit tests Lorenzo Iovino
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View file

@ -1,41 +0,0 @@
import { defineAuth } from '@aws-amplify/backend';
/**
* Define and configure your auth resource
* When used alongside data, it is automatically configured as an auth provider for data
* @see https://docs.amplify.aws/gen2/build-a-backend/auth
*/
export const auth = defineAuth({
loginWith: {
email: true,
// add social providers
externalProviders: {
/**
* first, create your secrets using `amplify sandbox secret`
* then, import `secret` from `@aws-amplify/backend`
* @see https://docs.amplify.aws/gen2/deploy-and-host/sandbox-environments/features/#setting-secrets
*/
// loginWithAmazon: {
// clientId: secret('LOGINWITHAMAZON_CLIENT_ID'),
// clientSecret: secret('LOGINWITHAMAZON_CLIENT_SECRET'),
// }
},
},
/**
* enable multifactor authentication
* @see https://docs.amplify.aws/gen2/build-a-backend/auth/manage-mfa
*/
// multifactor: {
// mode: 'OPTIONAL',
// sms: {
// smsMessage: (code) => `Your verification code is ${code}`,
// },
// },
userAttributes: {
/** request additional attributes for your app's users */
// profilePicture: {
// mutable: true,
// required: false,
// },
},
});

View file

@ -1,8 +0,0 @@
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
defineBackend({
auth,
data,
});

View file

@ -1,58 +0,0 @@
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
/*== STEP 1 ===============================================================
The section below creates a Todo database table with a "content" field. Try
adding a new "isDone" field as a boolean. The authorization rules below
specify that owners, authenticated via your Auth resource can "create",
"read", "update", and "delete" their own records. Public users,
authenticated via an API key, can only "read" records.
=========================================================================*/
const schema = a.schema({
Todo: a
.model({
content: a.string(),
})
.authorization([a.allow.owner(), a.allow.public().to(['read'])]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'apiKey',
// API Key is used for a.allow.public() rules
apiKeyAuthorizationMode: {
expiresInDays: 30,
},
},
});
/*== STEP 2 ===============================================================
Go to your frontend source code. From your client-side code, generate a
Data client to make CRUDL requests to your table. (THIS SNIPPET WILL ONLY
WORK IN THE FRONTEND CODE FILE.)
Using JavaScript or Next.js React Server Components, Middleware, Server
Actions or Pages Router? Review how to generate Data clients for those use
cases: https://docs.amplify.aws/gen2/build-a-backend/data/connect-to-API/
=========================================================================*/
/*
"use client"
import { generateClient } from "aws-amplify/data";
import { type Schema } from "@/amplify/data/resource";
const client = generateClient<Schema>() // use this Data client for CRUDL requests
*/
/*== STEP 3 ===============================================================
Fetch records from the database and use them in your frontend component.
(THIS SNIPPET WILL ONLY WORK IN THE FRONTEND CODE FILE.)
=========================================================================*/
/* For example, in a React component, you can use this snippet in your
function's RETURN statement */
// const { data: todos } = client.models.Todo.list()
// return <ul>{todos.map(todo => <li key={todo.id}>{todo.content}</li>)}</ul>

View file

@ -1,3 +0,0 @@
{
"type": "module"
}

View file

@ -1,109 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "es2022", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View file

@ -1,106 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"loreiov.com": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "iov",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/loreiov.com",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"server": "src/main.server.ts",
"prerender": true,
"ssr": {
"entry": "server.ts"
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "loreiov.com:build:production"
},
"development": {
"buildTarget": "loreiov.com:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "loreiov.com:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}

13
astro.config.mjs Normal file
View file

@ -0,0 +1,13 @@
import sitemap from "@astrojs/sitemap";
import tailwind from "@astrojs/tailwind";
import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
integrations: [tailwind(), sitemap()],
site: "https://lorenzoiovino.com",
compressHTML: true,
build: {
inlineStylesheets: "auto",
},
});

46
biome.json Normal file
View file

@ -0,0 +1,46 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"lineWidth": 100
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExtraBooleanCast": "error",
"noUselessConstructor": "error",
"useFlatMap": "error"
},
"correctness": {
"noUnusedImports": "off",
"noUnusedVariables": "off"
},
"style": {
"noNonNullAssertion": "warn",
"useConst": "error",
"useTemplate": "error"
},
"suspicious": {
"noExplicitAny": "error",
"noArrayIndexKey": "warn"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always"
}
}
}

18977
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,54 +1,31 @@
{ {
"name": "loreiov.com", "name": "lorenzoiovino.com",
"version": "0.0.0", "type": "module",
"scripts": { "version": "1.0.0",
"ng": "ng", "scripts": {
"start": "ng serve", "dev": "astro dev",
"build": "ng build", "start": "astro dev",
"generate-sitemap": "node ./node_modules/.bin/ngx-sitemap ./dist/loreiov.com/browser https://lorenzoiovino.com", "build": "astro check && astro build",
"watch": "ng build --watch --configuration development", "preview": "astro preview",
"test": "ng test", "astro": "astro",
"serve:ssr:loreiov.com": "node dist/loreiov.com/server/server.mjs" "format": "prettier --write .",
}, "format:check": "prettier --check .",
"private": true, "lint": "biome format src/",
"dependencies": { "lint:fix": "biome format --write src/",
"@angular/animations": "^17.0.0", "type-check": "astro check"
"@angular/common": "^17.0.0", },
"@angular/compiler": "^17.0.0", "dependencies": {
"@angular/core": "^17.0.0", "@astrojs/tailwind": "^6.0.2",
"@angular/forms": "^17.0.0", "astro": "^5.16.7",
"@angular/platform-browser": "^17.0.0", "tailwindcss": "^3.4.0"
"@angular/platform-browser-dynamic": "^17.0.0", },
"@angular/platform-server": "^17.0.0", "devDependencies": {
"@angular/router": "^17.0.0", "@astrojs/check": "^0.9.6",
"@angular/ssr": "^17.0.8", "@astrojs/rss": "^4.0.14",
"aws-cdk-lib": "^2.117.0", "@astrojs/sitemap": "^3.6.1",
"constructs": "^10.3.0", "@biomejs/biome": "^2.3.11",
"express": "^4.18.2", "prettier": "^3.7.4",
"ng2-fittext": "^1.4.0", "prettier-plugin-astro": "^0.14.1",
"rxjs": "~7.8.0", "typescript": "^5.3.0"
"tslib": "^2.3.0", }
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.8",
"@angular/cli": "^17.0.8",
"@angular/compiler-cli": "^17.0.0",
"@aws-amplify/backend": "^0.7.0",
"@aws-amplify/backend-cli": "^0.9.2",
"@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0",
"@types/node": "^18.18.0",
"autoprefixer": "^10.4.16",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"ngx-sitemap": "^1.0.0",
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0",
"typescript": "5.2.2"
}
} }

4519
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

3
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,3 @@
ignoredBuiltDependencies:
- esbuild
- sharp

2
public/astro.svg Normal file
View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_astro</title><path d="M5.9,18.847a7.507,7.507,0,0,0-.572,2.624,3.265,3.265,0,0,0,.551,1.553,7.427,7.427,0,0,0,2.093,1.681L13.1,28.119A7.332,7.332,0,0,0,15.2,29.287a3.239,3.239,0,0,0,1.5,0,7.381,7.381,0,0,0,2.117-1.16L24,24.711a7.512,7.512,0,0,0,2.117-1.688,3.241,3.241,0,0,0,.55-1.563,7.515,7.515,0,0,0-.587-2.643L21.547,4.551a3.973,3.973,0,0,0-.54-1.3,1.733,1.733,0,0,0-.7-.51,3.972,3.972,0,0,0-1.4-.122H13.005a3.932,3.932,0,0,0-1.4.125,1.713,1.713,0,0,0-.7.512,3.94,3.94,0,0,0-.535,1.3L5.9,18.848Zm13.24-13.2a3.329,3.329,0,0,1,.441,1.093l3.892,12.784a16.168,16.168,0,0,0-4.653-1.573L16.291,9.391a.331.331,0,0,0-.513-.169.323.323,0,0,0-.119.169l-2.5,8.557a16.14,16.14,0,0,0-4.674,1.579L12.393,6.743a3.281,3.281,0,0,1,.442-1.094,1.458,1.458,0,0,1,.582-.43,3.31,3.31,0,0,1,1.175-.1h2.793a3.314,3.314,0,0,1,1.176.1,1.454,1.454,0,0,1,.583.432ZM16.127,21.06a5.551,5.551,0,0,0,3.4-.923,2.8,2.8,0,0,1-.207,2.182A3.938,3.938,0,0,1,17.773,23.8c-.674.428-1.254.8-1.254,1.787a2.079,2.079,0,0,0,.209.914,2.49,2.49,0,0,1-1.535-2.3v-.061c0-.683,0-1.524-.962-1.524a1.028,1.028,0,0,0-.391.077,1.021,1.021,0,0,0-.552.551,1.03,1.03,0,0,0-.079.391,3.769,3.769,0,0,1-.988-2.644,4.206,4.206,0,0,1,.175-1.248c.4.757,1.92,1.32,3.731,1.32Z" style="fill:#ff5d01;fill-rule:evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 705 KiB

After

Width:  |  Height:  |  Size: 705 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 570 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

59
public/manifest.json Normal file
View file

@ -0,0 +1,59 @@
{
"name": "Lorenzo Iovino - Software Developer",
"short_name": "Lorenzo Iovino",
"description": "Software Developer based in Sicily. Passionate about technology, remote work, and life balance.",
"start_url": "/",
"display": "standalone",
"background_color": "#1e3a8a",
"theme_color": "#1e3a8a",
"orientation": "portrait-primary",
"icons": [
{
"src": "/favicon.ico",
"sizes": "48x48",
"type": "image/x-icon"
},
{
"src": "/photos/me.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/photos/me.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"categories": ["business", "productivity", "lifestyle"],
"lang": "en-US",
"dir": "ltr",
"scope": "/",
"shortcuts": [
{
"name": "Blog",
"short_name": "Blog",
"description": "Read my latest blog posts",
"url": "/blog",
"icons": [
{
"src": "/photos/me.png",
"sizes": "192x192"
}
]
},
{
"name": "Bio",
"short_name": "Bio",
"description": "Learn about my story",
"url": "/bio",
"icons": [
{
"src": "/photos/me.png",
"sizes": "192x192"
}
]
}
]
}

View file

Before

Width:  |  Height:  |  Size: 3.5 MiB

After

Width:  |  Height:  |  Size: 3.5 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 414 KiB

After

Width:  |  Height:  |  Size: 414 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3 MiB

After

Width:  |  Height:  |  Size: 3 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 939 KiB

After

Width:  |  Height:  |  Size: 939 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 519 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 11 MiB

After

Width:  |  Height:  |  Size: 11 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 421 KiB

After

Width:  |  Height:  |  Size: 421 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 706 KiB

After

Width:  |  Height:  |  Size: 706 KiB

Before After
Before After

32
public/robots.txt Normal file
View file

@ -0,0 +1,32 @@
# robots.txt for lorenzoiovino.com
User-agent: *
Allow: /
# Sitemaps
Sitemap: https://lorenzoiovino.com/sitemap-index.xml
Sitemap: https://lorenzoiovino.com/rss.xml
# Crawl-delay (optional, adjust if needed)
# Crawl-delay: 1
# Disallow paths (if any)
# Disallow: /admin/
# Disallow: /private/
# Allow specific bots
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: Slurp
Allow: /
# Block bad bots (optional)
User-agent: AhrefsBot
Crawl-delay: 10
User-agent: SemrushBot
Crawl-delay: 10

234
public/rss-styles.xsl Normal file
View file

@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:atom="http://www.w3.org/2005/Atom">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title><xsl:value-of select="/rss/channel/title"/> - RSS Feed</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #1f2937;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 2rem 1rem;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 1rem;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
color: white;
padding: 2rem;
text-align: center;
}
.header h1 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.header p {
font-size: 1.125rem;
opacity: 0.9;
}
.info-box {
background: #f3f4f6;
border-left: 4px solid #3b82f6;
padding: 1.5rem;
margin: 2rem;
border-radius: 0.5rem;
}
.info-box h2 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.75rem;
color: #1e3a8a;
display: flex;
align-items: center;
gap: 0.5rem;
}
.info-box p {
color: #4b5563;
line-height: 1.8;
}
.info-box code {
background: white;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-family: 'Courier New', monospace;
color: #1e3a8a;
font-size: 0.875rem;
}
.subscribe-btn {
display: inline-block;
background: #3b82f6;
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
text-decoration: none;
font-weight: 600;
margin-top: 1rem;
transition: background 0.2s;
}
.subscribe-btn:hover {
background: #2563eb;
}
.posts {
padding: 2rem;
}
.posts h2 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
color: #111827;
padding-bottom: 0.75rem;
border-bottom: 2px solid #e5e7eb;
}
.post {
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
transition: background 0.2s;
}
.post:hover {
background: #f9fafb;
}
.post:last-child {
border-bottom: none;
}
.post-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.post-title a {
color: #1e3a8a;
text-decoration: none;
}
.post-title a:hover {
color: #3b82f6;
text-decoration: underline;
}
.post-meta {
color: #6b7280;
font-size: 0.875rem;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.post-description {
color: #4b5563;
line-height: 1.8;
}
.tag {
background: #dbeafe;
color: #1e3a8a;
padding: 0.25rem 0.75rem;
border-radius: 0.375rem;
font-size: 0.75rem;
font-weight: 500;
}
.footer {
text-align: center;
padding: 2rem;
background: #f9fafb;
color: #6b7280;
font-size: 0.875rem;
}
.footer a {
color: #3b82f6;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
@media (max-width: 640px) {
body {
padding: 1rem;
}
.header h1 {
font-size: 1.5rem;
}
.info-box, .posts {
padding: 1rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📡 <xsl:value-of select="/rss/channel/title"/></h1>
<p><xsl:value-of select="/rss/channel/description"/></p>
</div>
<div class="info-box">
<h2>🎯 What is an RSS Feed?</h2>
<p>
This is an RSS feed. It allows you to subscribe to updates from this blog using an RSS reader.
Copy the URL <code><xsl:value-of select="/rss/channel/link"/>/rss.xml</code> and paste it into your favorite RSS reader to stay updated!
</p>
<a href="https://aboutfeeds.com/" target="_blank" class="subscribe-btn">Learn More About RSS</a>
</div>
<div class="posts">
<h2>Recent Posts (<xsl:value-of select="count(/rss/channel/item)"/>)</h2>
<xsl:for-each select="/rss/channel/item">
<div class="post">
<div class="post-title">
<a>
<xsl:attribute name="href">
<xsl:value-of select="link"/>
</xsl:attribute>
<xsl:value-of select="title"/>
</a>
</div>
<div class="post-meta">
<span>📅 <xsl:value-of select="substring(pubDate, 0, 17)"/></span>
<xsl:if test="author">
<span></span>
<span>✍️ <xsl:value-of select="author"/></span>
</xsl:if>
<xsl:if test="category">
<span></span>
<xsl:for-each select="category">
<span class="tag"><xsl:value-of select="."/></span>
</xsl:for-each>
</xsl:if>
</div>
<div class="post-description">
<xsl:value-of select="description"/>
</div>
</div>
</xsl:for-each>
</div>
<div class="footer">
<p>
Powered by <a href="https://astro.build" target="_blank">Astro</a>
Visit <a>
<xsl:attribute name="href">
<xsl:value-of select="/rss/channel/link"/>
</xsl:attribute>
lorenzoiovino.com
</a>
</p>
</div>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View file

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 772 B

Before After
Before After

View file

@ -1,58 +0,0 @@
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');
const commonEngine = new CommonEngine({
enablePerformanceProfiler: process.env['PERFORMANCE_PROFILER'] === 'true',
});
server.set('view engine', 'html');
server.set('views', browserDistFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(browserDistFolder, {
maxAge: '1y'
}));
// All regular routes use the Angular engine
server.get('*', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine
.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
run();

View file

@ -1,8 +0,0 @@
<div class="flex flex-col h-screen justify-between">
<header class="inset-x-0 top-0 z-50 bg-secondary">
<iov-menu></iov-menu>
</header>
<router-outlet>
</router-outlet>
<iov-footer></iov-footer>
</div>

View file

@ -1,29 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'loreiov.com' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('loreiov.com');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, loreiov.com');
});
});

View file

@ -1,32 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import {RouterOutlet} from '@angular/router';
import {MenuComponent} from "./menu/menu.component";
import {HeroComponent} from "./hero/hero.component";
import {SectionComponent} from "./section/section.component";
import {FooterComponent} from "./footer/footer.component";
import {Meta} from "@angular/platform-browser";
@Component({
selector: 'iov-root',
standalone: true,
imports: [CommonModule, RouterOutlet, MenuComponent, HeroComponent, SectionComponent, FooterComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
constructor(private metaTagService: Meta) {
this.metaTagService.addTags([
{ name: 'description', content: 'Lorenzo Iovino - Software Developer' },
{
name: 'keywords',
content: 'Lorenzo Iovino, lorenzoiovino.com, Software Developer, Software Engineer, Sicily, Computer Science, Blog, Personal Page',
},
{ name: 'robots', content: 'index, follow' },
{ name: 'author', content: 'Lorenzo Iovino' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ charset: 'UTF-8' },
]);
}
}

View file

@ -1,11 +0,0 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering()
]
};
export const config = mergeApplicationConfig(appConfig, serverConfig);

View file

@ -1,9 +0,0 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideClientHydration()]
};

View file

@ -1,57 +0,0 @@
import { Routes } from '@angular/router';
import {BiographyPage} from "./pages/biography/biography.page";
import {BlogPage} from "./pages/blog/blog.page";
import {ProjectsPage} from "./pages/projects/projects.page";
import {HomePage} from "./pages/home/home.page";
import {PortfolioPage} from "./pages/portfolio/portfolio.page";
import {ContactMePage} from "./pages/contact-me/contact-me.page";
import {PageComponent} from "./page/page.component";
import {LikeDislikePage} from "./pages/like-dislike/like-dislike.page";
import {DisclaimerComponent as PortfolioDisclaimerComponent} from "./pages/portfolio/disclaimer/disclaimer.component";
export const routes: Routes = [
{
path: '',
component: HomePage
},
{
path: '',
component: PageComponent,
children: [
{
path: 'biography',
component: BiographyPage,
},
/*{
path : 'portfolio',
component: PageComponent,
children: [
{
path: '',
component: PortfolioPage,
},
{
path: 'disclaimer',
component: PortfolioDisclaimerComponent,
}
]
},
{
path: 'projects',
component: ProjectsPage,
},
{
path: 'like-dislike',
component: LikeDislikePage,
},
{
path : 'blog',
component: BlogPage,
},
{
path: 'hello',
component: ContactMePage,
}*/
]
}
];

View file

@ -1,5 +0,0 @@
<div class="arrow left-1/2" (click)="scrollDown()" *ngIf="visible">
<span></span>
<span></span>
<span></span>
</div>

View file

@ -1,38 +0,0 @@
.arrow {
position: absolute;
transform: translate(-50%, -50%) rotate(0deg);
cursor: pointer;
}
.arrow span {
display: block;
width: 1.5vw;
height: 1.5vw;
border-bottom: 5px solid white;
border-right: 5px solid white;
transform: rotate(45deg);
margin: -10px;
animation: animate 2s infinite;
}
.arrow span:nth-child(2) {
animation-delay: -0.2s;
}
.arrow span:nth-child(3) {
animation-delay: -0.4s;
}
@keyframes animate {
0% {
opacity: 0;
transform: rotate(45deg) translate(-20px, -20px);
}
50% {
opacity: 1;
}
100% {
opacity: 0;
transform: rotate(45deg) translate(20px, 20px);
}
}

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ArrowScrollDownComponent } from './arrow-scroll-down.component';
describe('ArrowScrollDownComponent', () => {
let component: ArrowScrollDownComponent;
let fixture: ComponentFixture<ArrowScrollDownComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ArrowScrollDownComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ArrowScrollDownComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,28 +0,0 @@
import {Component, HostListener} from '@angular/core';
import {NgIf} from "@angular/common";
@Component({
selector: 'iov-arrow-scroll-down',
standalone: true,
imports: [
NgIf
],
templateUrl: './arrow-scroll-down.component.html',
styleUrl: './arrow-scroll-down.component.scss'
})
export class ArrowScrollDownComponent {
visible: boolean = true;
scrollDown() {
window.scrollBy({
top: window.innerHeight,
behavior: 'smooth'
});
}
@HostListener('window:scroll', ['$event'])
checkIfPageIsStillOnTop() {
this.visible = window.scrollY <= 100;
}
}

View file

@ -1,3 +0,0 @@
<pre>
{{photos[index]}}
</pre>

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AsciiPhotoComponent } from './ascii-photo.component';
describe('AsciiPhotoComponent', () => {
let component: AsciiPhotoComponent;
let fixture: ComponentFixture<AsciiPhotoComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AsciiPhotoComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AsciiPhotoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,166 +0,0 @@
import {Component, Input} from '@angular/core';
@Component({
selector: 'iov-ascii-photo',
standalone: true,
imports: [],
templateUrl: './ascii-photo.component.html',
styleUrl: './ascii-photo.component.scss'
})
export class AsciiPhotoComponent {
@Input() index: number = 0;
photos = [`
**#*+====*#%%@@%%@%%+==+#######*++*##*+*++*#%*=========================+++*+*++++++=+===============
*##*======+###%%@%%%+==+*##%%%##++###*+*=+##*=====================+++**#*******#*+++++==============
##*+=======+***%%@%%#==++#%%%%##**###*++++#*+==++==============++***##*+=====++=++**++==============
*#++=========****#%%##%**#%%%%##*####++=+*##==++++====++=====++*####*===============+===============
**++======--==+*#%%#%%%*+#%%%%%%###**+=+*######**++***====+**#####+=================================
**++*+++=+=====+*##%%%%#+*#%%%%%###***+**###*=++*##*+=-==**####++++=====-===========================
******####*+++==+**##%%#*+*#%%%%##***+*#%##++**##*=====+**##+=++++===--===-----------------=========
**+**#++==+*#%#**+#+#%%%##*#%%%%%#*#*+#%##*##*#+===-=*+*#*===+***++++==-----------------------------
*+++===-====-=+####*###%##*##%%%%#*##*####*+#*=-===+*##+-==+**###*+=++================--------------
+*##+=-==========#####*#######%%%#*#*####+##+---==+**=-==++####++=+##+*+**####***+=-----------------
#++=+##+******+**++#%#########%%%%##*###+*#=----++*+--==+**%##*###############****+====-------------
#%#+*++##*++------+**#%#+#####%%%##**##***----=+*+--==***##*####*+===------=+++*####*++==-----------
*####%#+==*+=-------+###**#####%%##**#*+*=---==*=--++*####*+++==-------------====*###*=-------------
===**#%#*++=+==-------*#######%%%#######+--==++--=+=*#+=--------------------==--=+++**=-=-----------
---=----+*#*+=+=---====+##*##%%%%#%####**+**##*+=+**=-=----------:::::::::-=++--====+*++---:::::::--
------------=**+*=======+#**#%###%%######*+*+=-**+---:-----::::::::::::::::-==----====+==--:::::::::
-=======-=++=++*#**++=-==+#*#%#######+###**+==++=*=====---:::::::::::::::::-++==--=-===*++====+++===
=+**********+--=+***++--===++#####%**##*#*=+++###%%%%%@#=-================+=======++==++*++=--------
*+-::::-==+++++=---++*+----+=*#*###**#**++*#%%%%%%%%%@@@%******+++==-----------------==++====-------
++===+=+===+====+++==*+++===**#*##*####+*#%%%%%%%%%%%%**#%%%%%%%%%%%%#%%#-----------=-=+====------==
+*****+===-=---------==****+*##*###+*###%%%%%%%%#%###+++*%%%@@%%%%%%@@@@@#+++++++++**+**+*+++++=++++
-:-----:------==--::::-+*##*=#%##%%#%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%@@@%%%##*++**+=++*+++++==+++===
:--========+======+*++**######%%%#%%%%%%%%%%%%%%@@@@%#####%%%%%%%@@@%%%%%@@%###*+====-====--=----==-
+*++++***+++++++**+**####%%%%%%#%@%%%%%%%%%%#**+++++++++++++++*#%@@@@@@@%@%%%**##**==---------------
=+===+=--=+=-===-=**###%%%%%%%##%%%%%%%%%#++=========+++==++++++++**#%@@@@%%#+--#*++=---------------
-------------=+*######**%%%%%%%#%%%%%%#++===================+++++++++**%@%%%%#+-:+##++=---:::--::-::
:::::------:-*%##=###+***+%#@@%%%%%%*++===========================+++++**%@@@%%*---##+=---:-----====
::::--::::-=**#*+#%**++*++%@%%%%%@%*++============================+++++++*#%@@%#=-:-*##*++++++++++++
-:----:::=*#*#**##**+++*+=#@%%%%@#+++===============================+++++++*%@@%*:::-+***=----::----
:::---:-=##**#-*#*=--:=*:::-#%@%*++++==============================+++++++++*%%@%=:::-+*+=----------
--------+###*++##*=---=*---+%%#++++++================================++++++++*%@%*::::=***#*########
::::-:-+##*++=**#=+########%%#+++++++================================++++++++*%%@%-::::=******#*****
-:::--+#*#*=-=#+*-+******##%%*+++++===============================+++++++++***#%@%-:::::=*****##****
:-::-+###*=:-=*+--+******#%%#++++++++****#######*++++===========+++++++++++****%%%-::::-:*****##****
::---****=-::--=:-*******#%%*++++++*#%%%%%%%%%%%###**+++++++++++******+++++++**#%#-::::::+*****#****
-::-=*+++-::::--:-******##@%*++++*#####*******######**+++++++**###%%%%%###**+**#%+::::::--*****#**++
:::-=*+=-::::::::-******#%@%*+++*####*********######**+===++*###%%%%%%%%%%%%#**#%=::::::--+****#****
-::-+*=-:::::::::=******#%@%*+++**####%%%%%%%%%%%%#**++====+*##%%#########%%%#*%#:::::::::=****##***
::--++--:::::::::+******#*@#++++***##%%%#%@@@@##%%#**+======+#%%%%%########%%%#%-:::::::-:-*****#***
---------:::::---**++***#*##+++++***********####****++======+*%%#%@@@@@%%%#####*::::::::::-*****#***
---:::-:::::::---**++****++*+++++++++************+++=========**#####%##%%%%##*#=::::::::---*******++
-----------:::---#*+++++#*+*++++======+++*****+++============+****#*########***=========++++++++++++
=============++==+====+*****+++=========================--===+*****#***********+----:-----::----::::
==============--=-----+*++*#*++++================+====----====++++++***+++++++**=:::-::::--::::::-::
------=----------::--:=++*##**+++============+++=====------====+++++++++++++++**---------========--=
------=--------=------=+**#%#**+++=++=====++++++======-----===++**++++++=+++***+=++++++*++++++++++++
*+++++::::::--:::...::-+++*%#**+++++++++++**+=+++++++++=====+++*+**+++++++++**#+++++*+++++++++++===+
*##**+::::::::::::-=+==+++*%%#*++*#*+++***+====+*%%%%#**++++*****+++++++*++**##=----------------::::
==+++-::::::::::=*#**#**+++#%#*++**++***++++++++*##########%@%#**++++++******%#-:::::::::-::--:-----
--=--:::::::::::+**###*##+*%%#*****+*#****#####%#%%%%%%%%%%@@%%#**+++**+****##+:::::::::-:::-::::-::
==--=::::::::::-+******#@@@%%#*+***+*####%%%%%%%%%%%%%%%%%@%@%%%%####*******##=--:::::-:-::::::::--:
---=-::::::::::=+******#@@@@%#***##*###%%%%%#######**##%%##%%%%%%%%%%##*****##+-:-::::--::--------=-
-----:::::::::-=======-*@@@@%%#*###**##%@%%##%#################%%%%%%%%#######=-------------------:-
-----::--:==--=+=--::::*@@@@%%###%%#*##%#**++++++++++++++++**###%%@@@%%######+=:------------:--:-:--
----:::---:-::===--::::=%@@@@%%##%%%###**+++++++**++++++++++++*****#%%##%%%##=-:::::--------:-::----
--=-:::::::::::-========*@@@@@%%%%%%%###**++==++*##############*****#%##%%%%+-:::::::--------------=
----::::::::::::::::::::::*%@@@%%%%%%%%%##*++++++***##%%%%###**++***#%%%%%%*-::::::::-:::-----=--=*#
----:::::::::::-:::::-:::-=*#*#@@%%%%%%%%##*+++++++**#######**++****#%%%%%%=::::::::---:::::---=*###
--=-==++++*#########%%%%%%###***%%%%%%%%%##*+++++++++********++***#%%%%@@%+-----::-------------***##
----==++=+#%%%@%%%%%%%%%%%#****+*%%%%%%%%%%#****++++++++++*+*++**#%%%%%@#=---------------------*####
----=-====################****+++**%@@%%%%%%##**#****++****#***###%%%%@%+----:-----------------+#*%#
=-=======+#%%%%%%%%%%%%%%%#****++***%@@@@%%%%%######******#####%%%%%@@#++---::::::-:::-:-------=+***
-========+#%%@@@@%%%%%%%%%#****++++**#%%%%%@%%%%%############%%%%@@@@%###*************+::------=*++*
---=-==--+#%%%@@%%@%*##%%#****++++++**##%@@@@@@%%%%%%%%%%%%%%%%@@@@%#*%%%%%%%%%%%%%%%%#--------=*#*#
+++++*+++*#########****##****+++++++++**#%@@@@@@@%%%%%%%%%%%%@@@@%#***%%%%%%%%%%%%%%%%#========-++*+
--=-=====*##%%%@%%@#####+**++++++++++++***##%@@@@@@@@%@@@@@@@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%##+++*##*
==+=====+#%#%%%%%%#+==--=*+++++++++++++++*****#%%@@@@@@@@@@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%=+##*
========+#%#%%#+===-==---++++++++++++++++++******##########**#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+=***
--======*#*=====+=--==----+++++++++++++++++++****************#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+=***
=-=============++===-==---=**++++++++++++++++++**************#%%%%%%%%%#%%%%##%%#%#%%#%%%%%%%%%+=+++
=======---===+++====--=----=**+++++++++++++++++++***********+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*=+*+
========--=++++======-===----+*+++++++++++++++++++++++****+++#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+=+##
=====+==--=+++=========-==-----++++++++++++++++++++++++++*+++#%%%%%%%%%%%%#%%%%%%#%#%%%%%%%%%%%+====
======+==--=++===-======-==-----=++++++++++++++++++++++*+++++#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+====
=======+==-=++===-=======--==------=**+++++++************+++*#=-------------------------------*+=+==
===========-+++===-=======---=--------=++++**************++*+#-::::::=-=--=--==---==-:=-::::::*+=++=
=====+======+++======-=====----=---------==++******#***++====*-:::::-:---:-::---:-:--:=-::::::*+=++=
====++==---=+++===-==-=======----==-----=====================*-:::::::::::::::::::::::::::::::*+==++
====++===--==+++=====---========----==-----==================*-:::::::::::::::::::::::::::::::*+==++
===+++====-==+++======--============--====----===============*-:::::::::::::::::::::::::::::::*+==++
+++++++==--==+*+=======--====================================****=-:::::::::::::::::::::::-+*+#+==++
+++++++==---=+*++=======-=====================================##+-:=+#*=-:::::::::::-+*#+-:-+#*===++
+++++++==--==+*++========--=======================================+***+====+*+++++=-=+***++=++====++
+++++++==---=+*++========================================================+*##+=*##*+++======+++====+
++++++====--=+*++===================================================================++=======++====+
+++++++===--==*++===================================================================+++=======+++==+
+++++++===--==*++===================================================================+++========+++=+
==+++++==---==*++===================================================================+++=========++++
==+++++===--==**++===========================================++=====================+*+==========+++
===+++++===-==**++=+==+===================+=++===============+==================+===+*++===+====++++
+===++++======**++++=+++=========+==============+++=========++=================++===+*++========++++
+===+++++=====+*++++++++==================================++++=================+++==+**++=+====+++++
+====++++=====+*+++++++===========++==+=======+===++=+===+++++================++++===+*++=++===+++++
+====++++=====+*+++++++==========+++++++==+=++=+++++====+++++===============+=++++===+*++==+=++++*++
++++=++++=====+*++++++============+++++==++++++++++++++++++++================++++++==+*+++==+++++*++
++++==+++=====+*++++++++==========+++++++=+++++++++++++++++++======+========+++++++==+*+++=++++++*++
*++++=+++=====+*+++++++==========++++++++++++++++++++++++++++++++===========+++++++==+*+++++++++*+++`,
`
´¦)vòri¯\`
\`­ceåëëýýÖÒÞŸj°
\`¯ôÚðÒkµ5ç±Cò‰öVµ0)´
?§åÝú±sƒorvvjvjctu2sº
>DU©óJIrì[[[7>ï1c¤JÌ0¡\`
tÓxLÌò¼¤jl<[¿[[¿7ì1o%Ìw\`
¨žå©çVósƒrï?¿[¿ïcJó±ò©Í:\`
Þxúô20CIîíiiïcCΞ5ùaaÍsÎï·
äf5õaÍönôx5©¢í=ÏhZÿÎ2Í@½y½ò¨
Jšyf&6àkÎUDÒµ=ï%@03½trî%Ï5C;
~ÇTOSZád$àwVçÍI[ïvjrîoc%Ïkçcˆ
iZy9ààžyôóuLLJ?/[1IJsu@CóUyj
\`£Çx5Vs¤î1oó2eS5§Úx5aaLCÌüÝr…
\`shSξç@u‡z0šGŽÐéñSUhbá¾f2ô•
\`~TñèDŸ6T5ÏÍyñÓýšL‰ƒ½tcuõž6x¡
·+9ÓmëkUSfÇâpDµõykUCcrsfÝžS¹
·*üámÓTkFášOúúaYV%ïjuO§žì\`
\`ˆ¯Ddñ$m8èP6™YC¤oCxhb8ôv´
¨?ÕqÕAAqA$ÙÿZäåðŠA¾;
|ZgXgHÁ#Nþæ##ê®Í½1i¡\`
Vx§áåðH#ê$Ÿõ£sIí>׫˜¨\`
!bõYaõOUT¾úY±ò½%l[?×?[òÇŸ>\`
¨|OÕæÛóóÍ0©ü2çù0sztí7}×׿7@åèdÓFä9ö|\`
\`ÏÜÔœÁNÅBp3nòsnC±@¢‡o=[++?*׉áëÚ$ýåÞÞÿDZähkVª¨
\`°ÏñÞëð XÄ#æQHšCs½‡½zJtj=†}†[†CèܶðëÙÙ$ÚdèÖåÞFáñDhôv
\`^2FÞÙýëððøâããßqŽþÁ#ÁXS3öö@Ìöƒìi=wÙG¶ãéëddëÚéëýÙÜÞÖÓdÞåDPûí…
\`’‡áÞë$ë$éé8¶¶ððG¶¶ãßÐÄNÁEþRKgÛÔApmmA€âøÚ$$$ë8¶ð$$éÜýýmëýëÙèFû>·
\`/Pè8$$ø€âÜ8Úðø88¶mâ¶ø¶pÛRÄÀHXœÐÛÛÕÕp¶ððéÚ8Ü$ÚðGGÚéøÚëpÜýâ¶ëÞbá§•\`
\`÷šådÚø¶ðÚÔqÚ$8¶ðÚðGââ¶ðøð¶ãÔÐqmøðéðððéééÚéé8Ü$éøß8¶¶Ú¶GÜ€âÜëÜÙÿäž¹
;µåGéŠÔÜÚGðÜéðéøøðéømpÔÕAââGðøðððÚÜé8Ú8éâøãGðߊßðGðëýÞDš2
\`¿àè$Gâð¶mø¶¶ÐøéøðÚ8éÜðéé88ðøðmmÔÔpâøøéðÚÚðééð8ééGÔâpã¶ãqÛãßâÚÜ$ÞÒ¥Ý*
ôFëÚðpÔøøÔâGpøøÚéðéÚÚÚÚ8ðéÚ8mppãð8Ú8Ü8ÚÚÚ8ÚðÕmßãœAÔââÚ$$ÙÓš2
\`[ûÞÜðø¶pÕããKmAãø¶øÚÚÚéÚÚÚÚÚÚðéøéð¶â¶øð8é888Ú8ðéÜ8¶AmÔêAêqŠßAGøãøëýÓ¥e”
LåëpÛAßÄÔðééééðð8Ú88ÚÚéðøøGéðððð8Úéé8ééâpÔœêHÐÔÐmÕßéÚÚ$ÓFš±
4Ùððé8ÜøÔêÐAâKðøøøøð8éÚÜÚ8Üøøðøéððð8ééðððøpAAœXHŽHAÔéé8ÜdýÿZ®I·
edéððG88âÐÀgÔêGãmãøøøéðÚÚÜÜéøøøøøâéøéðéðøøðømAXKÀÄÕøéøøÜ$$dÖbZú¯
\`[ûÜéßâÚðããðÚ¶RÁRmmßßmm¶øéÚÚð8éÜéðø¶âââ¶¶G¶øðé8éøðøGmßÔŽÄNœA¶ãpâé88ÜëÞÿbÝ<\`
sÿÙÜðAÕGÜÚðð88ÔEppppâGé888éÚé8øâããâââøøðÚéøøãpÔêHþEqããÔâðøéÜëdýÖPz
çÙÙ$88GAßøÚÚéðãgÛAÔÔApãGðð8éøðããããããGømâãAŠgÀÁEÕßÔã$DàÓð88é$ýFU}·
²ôÚÚëÚééømm8ë$GggÔÕqŠÔmãGGøðøøâmmmmmmâããâããßpßÔÐRþæÁÛD±ccµ9nj¾8dåZ^\`
¡mGÜÚÚé8ððÜéÔŽqêgêÛÔApmãâãGãGâãâmppppppßßÔÔÕqÐŽÀNQãõjïI¤ííïoJ5Ü8èÒP¼
(kÙ8ÔÔGÚ8ð88ðÚémKgœŽœÐêÛÔpmmmmmãppßAÔÔÔAÔÔÔÔŠqgœHEE¥uvo¼l[[íƒzovJÝð$ýb9^\`
´iPÙÚøøqÔâéðøéðéâHHHŽœgêêÛÕpmpßmßAAÔÔÛÕÕŠÛÛÛŠêgœREŽŸ½¢%%î1j<>jr¼ÒGðÜýÒPï·
rÒdðøøGmÔŠøéâøðâÄNÀXXXggÐêÕppAAÔÔAÔÔÔÕŠÛêqÛqqêÐœŽœý0z½¤t½ti<tƒrùÖããøÙFò
ˆƒÓâââãâðÚ$ðÖeùÍ2§8ÛêÛm8ÔmŠÔAÕqŠqÐÐÐÐÐggœœKë½trVó3Y©ùfP$ãð$ëÙÞ¥ô˜
¼ýÚé8ððøãpmmÚf1í=í=írjrzÇédááÕmëéÙdéGèýýøðãÚAðÙhfõ6eÝÖðâÙdëëéãGâŠêéÒÒFU
\` sÜøGéé €ßÕЊÔÓ½1í=ïìíí=íÍGAZŸðÚÙáãýëøø$Ü$GãëÜ$ëðãëÙé8Ú8ð8éãÔßéøâããmããâmébDk¹
\` nÜãããpêKEHRXÛßÔ¶w%oí<<<[7=©øÚÙÿhhýgøé¶m¶8ø¶mÔG¶ø$éßmÚÜø¶øðø¶âãÔÕâðGp€Am¶øø$¥h
öÚãÕÕßpmmmâGðéÿf&Lc=ì<<<üÍ©OÚqmâppÕã8ðâpÔAmßméðpGÜãâGâmÔßøãð8Öh°
\`\` ½ÜâAÛgêÔÔApmãmã¶Gøø8äyúüüüaúõúx߀AAÔŠqÐÔð$Ú¶AÔAAÛÔ¶ðøéÚ$Úøâ¶Gâââã€Ap¶øø¶¶éÜèŸ;`]
}

View file

@ -1,5 +0,0 @@
<a href="{{url}}" class="mr-2 text-lg px-4 sm:px-12 py-4 rounded-full mt-4 font-bold
ring-white text-white bg-secondary ring-2
hover:ring-accent hover:text-accent hover:bg-transparent hover:ring-2">
<ng-content></ng-content>
</a>

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ButtonCtaComponent } from './button-cta.component';
describe('ButtonCtaComponent', () => {
let component: ButtonCtaComponent;
let fixture: ComponentFixture<ButtonCtaComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ButtonCtaComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ButtonCtaComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,12 +0,0 @@
import {Component, Input} from '@angular/core';
@Component({
selector: 'iov-button-cta',
standalone: true,
imports: [],
templateUrl: './button-cta.component.html',
styleUrl: './button-cta.component.scss'
})
export class ButtonCtaComponent {
@Input() url: string = '';
}

View file

@ -1,17 +0,0 @@
<div class="relative bg-clip-border {{borderRounded ? 'rounded-2xl' : 'rounded-0'}} shadow-2xl shadow-accent bg-white text-gray-700 w-full items-center text-center">
<div class="grid grid-cols-3 justify-items-center bg-slate-100 p-4">
<div class="text-2xl font-bold text-center self-center">
<ng-content select="[left]"></ng-content>
</div>
<div class="self-center">
<ng-content select="[center]"></ng-content>
</div>
<div class="self-center">
<iov-button-cta
[url]="ctaUrl">
<ng-content select="[right]">
</ng-content>
</iov-button-cta>
</div>
</div>
</div>

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CardCtaComponent } from './card-cta.component';
describe('CardComponent', () => {
let component: CardCtaComponent;
let fixture: ComponentFixture<CardCtaComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CardCtaComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CardCtaComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,18 +0,0 @@
import {Component, Input} from '@angular/core';
import {ButtonCtaComponent} from "../button-cta/button-cta.component";
@Component({
selector: 'iov-card',
standalone: true,
imports: [
ButtonCtaComponent
],
templateUrl: './card-cta.component.html',
styleUrl: './card-cta.component.scss'
})
export class CardCtaComponent {
@Input() color: 'light' | 'dark' = 'light';
@Input() ctaUrl: string = '';
@Input() borderRounded: boolean = true;
}

View file

@ -1,37 +0,0 @@
<div class="w-full overflow-hidden block">
<svg class="fish" id="fish">
<path
id="fish2"
d="m 172.04828,20.913839 c 0.0489,-0.444179 -0.2178,-0.896934 -1.01784,-1.415715 -0.72801,-0.475049 -1.4826,-0.932948 -2.2149,-1.401138 -1.6035,-1.028129 -3.29018,-1.969653 -4.89798,-3.079244 -4.67074,-3.24131 -10.22127,-4.404923 -15.76322,-5.1509392 -2.27235,-0.286401 -4.81223,-0.168925 -6.72186,-1.574351 -1.48174,-1.081294 -4.04993,-4.828523 -6.86506,-6.456038 -0.4862,-0.290688 -2.77227,-1.44486897 -2.77227,-1.44486897 0,0 1.30939,3.55000597 1.60951,4.26429497 0.69542,1.644664 -0.38158,3.063809 -0.83262,4.642447 -0.29069,1.0418502 2.13772,0.8129002 2.26463,1.7827212 0.18179,1.432007 -4.15197,1.936211 -6.59152,2.417263 -3.65634,0.715146 -7.91635,2.082841 -11.56925,0.884071 -4.3046,-1.38313 -7.37269,-4.129669 -10.46566,-7.2354952 1.43801,6.7252892 5.4382,10.6028562 5.6157,11.4226162 0.18607,0.905509 -0.45961,1.091584 -1.04099,1.682394 -1.28967,1.265655 -6.91566,7.731125 -6.93366,9.781383 1.61379,-0.247815 3.56115,-1.660957 4.9803,-2.485862 1.58035,-0.905509 7.60593,-5.373029 9.29347,-6.065023 0.38587,-0.160351 5.0549,-1.531476 5.09434,-0.932949 0.0695,0.932949 -0.30784,1.137031 -0.18436,1.527189 0.22638,0.746016 1.44144,1.465449 2.02282,1.985088 1.50918,1.292237 3.21044,2.42841 4.27373,4.156252 1.49203,2.401827 1.55805,4.999163 1.98251,7.677102 0.99469,-0.111473 2.0091,-2.17545 2.55961,-2.992638 0.51278,-0.772598 2.38639,-4.07136 3.09725,-4.275442 0.67227,-0.204082 2.75511,0.958673 3.50284,1.180763 2.85973,0.848057 5.644,1.353976 8.56032,1.353976 3.50799,0.0094 12.726,0.258104 19.55505,-4.800226 0.75545,-0.567658 2.55703,-2.731104 2.55703,-2.731104 0,0 -0.37644,-0.577091 -1.04785,-0.790605 0.89779,-0.584808 1.8659,-1.211633 1.94993,-1.925922 z"
style="fill:#528484;fill-opacity:1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccc" />
<path
sodipodi:nodetypes="cccccccccccccccccccccccccccccccc"
inkscape:connector-curvature="0"
style="fill:#528484;fill-opacity:1"
d="m 234.99441,42.953992 c 0.0489,-0.44418 -0.2178,-0.896934 -1.01784,-1.415715 -0.72801,-0.47505 -1.4826,-0.932949 -2.2149,-1.401138 -1.6035,-1.02813 -3.29018,-1.969653 -4.89798,-3.079245 -4.67074,-3.24131 -10.22127,-4.404923 -15.76322,-5.150939 -2.27235,-0.286401 -4.81223,-0.168925 -6.72186,-1.574351 -1.48174,-1.081294 -4.04993,-4.828523 -6.86506,-6.456038 -0.4862,-0.290688 -2.77227,-1.444869 -2.77227,-1.444869 0,0 1.30939,3.550006 1.60951,4.264295 0.69542,1.644664 -0.38158,3.063809 -0.83262,4.642447 -0.29069,1.04185 2.13772,0.8129 2.26463,1.782721 0.18179,1.432007 -4.15197,1.936211 -6.59152,2.417263 -3.65634,0.715146 -7.91635,2.082842 -11.56925,0.884072 -4.3046,-1.383131 -7.37269,-4.12967 -10.46566,-7.235496 1.43801,6.725289 5.4382,10.602857 5.6157,11.422617 0.18607,0.905508 -0.45961,1.091583 -1.04099,1.682394 -1.28967,1.265654 -6.91566,7.731125 -6.93366,9.781382 1.61379,-0.247815 3.56115,-1.660957 4.9803,-2.485862 1.58035,-0.905509 7.60593,-5.373029 9.29347,-6.065023 0.38587,-0.160351 5.0549,-1.531476 5.09434,-0.932948 0.0695,0.932948 -0.30784,1.137031 -0.18436,1.527188 0.22638,0.746016 1.44144,1.46545 2.02282,1.985088 1.50918,1.292237 3.21044,2.42841 4.27373,4.156252 1.49203,2.401827 1.55805,4.999163 1.98251,7.677102 0.99469,-0.111473 2.0091,-2.17545 2.55961,-2.992638 0.51278,-0.772598 2.38639,-4.071359 3.09725,-4.275442 0.67227,-0.204082 2.75511,0.958673 3.50284,1.180763 2.85973,0.848057 5.644,1.353976 8.56032,1.353976 3.50799,0.0094 12.726,0.258104 19.55505,-4.800226 0.75545,-0.567658 2.55703,-2.731104 2.55703,-2.731104 0,0 -0.37644,-0.57709 -1.04785,-0.790605 0.89779,-0.584808 1.8659,-1.211633 1.94993,-1.925921 z"
id="fish3" />
<path
id="fish6"
d="m 200.07076,80.737109 c 0.0489,-0.44418 -0.2178,-0.896934 -1.01784,-1.415715 -0.72801,-0.47505 -1.4826,-0.932949 -2.2149,-1.401138 -1.6035,-1.02813 -3.29018,-1.969653 -4.89798,-3.079245 -4.67074,-3.24131 -10.22127,-4.404923 -15.76322,-5.150939 -2.27235,-0.286401 -4.81223,-0.168925 -6.72186,-1.574351 -1.48174,-1.081294 -4.04993,-4.828523 -6.86506,-6.456038 -0.4862,-0.290688 -2.77227,-1.444869 -2.77227,-1.444869 0,0 1.30939,3.550006 1.60951,4.264295 0.69542,1.644664 -0.38158,3.063809 -0.83262,4.642447 -0.29069,1.04185 2.13772,0.8129 2.26463,1.782721 0.18179,1.432007 -4.15197,1.936211 -6.59152,2.417263 -3.65634,0.715146 -7.91635,2.082842 -11.56925,0.884072 -4.3046,-1.383131 -7.37269,-4.12967 -10.46566,-7.235496 1.43801,6.725289 5.4382,10.602857 5.6157,11.422617 0.18607,0.905508 -0.45961,1.091583 -1.04099,1.682394 -1.28967,1.265654 -6.91566,7.731125 -6.93366,9.781382 1.61379,-0.247815 3.56115,-1.660957 4.9803,-2.485862 1.58035,-0.905509 7.60593,-5.373029 9.29347,-6.065023 0.38587,-0.160351 5.0549,-1.531476 5.09434,-0.932948 0.0695,0.932948 -0.30784,1.137031 -0.18436,1.527188 0.22638,0.746016 1.44144,1.46545 2.02282,1.985088 1.50918,1.292237 3.21044,2.42841 4.27373,4.156252 1.49203,2.401827 1.55805,4.999163 1.98251,7.677102 0.99469,-0.111473 2.0091,-2.17545 2.55961,-2.992638 0.51278,-0.772598 2.38639,-4.071359 3.09725,-4.275442 0.67227,-0.204082 2.75511,0.958673 3.50284,1.180763 2.85973,0.848057 5.644,1.353976 8.56032,1.353976 3.50799,0.0094 12.726,0.258104 19.55505,-4.800226 0.75545,-0.567658 2.55703,-2.731104 2.55703,-2.731104 0,0 -0.37644,-0.57709 -1.04785,-0.790605 0.89779,-0.584808 1.8659,-1.211633 1.94993,-1.925921 z"
style="fill:#528484;fill-opacity:1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccc" />
<path
sodipodi:nodetypes="cccccccccccccccccccccccccccccccc"
inkscape:connector-curvature="0"
style="fill:#528484;fill-opacity:1"
d="m 77.275623,89.018799 c 0.0489,-0.44418 -0.2178,-0.896934 -1.01784,-1.415715 -0.72801,-0.47505 -1.4826,-0.932949 -2.2149,-1.401138 -1.6035,-1.02813 -3.29018,-1.969653 -4.89798,-3.079245 -4.67074,-3.24131 -10.22127,-4.404923 -15.76322,-5.150939 -2.272347,-0.286401 -4.812227,-0.168925 -6.721857,-1.574351 -1.48174,-1.081294 -4.04993,-4.828523 -6.86506,-6.456038 -0.4862,-0.290688 -2.77227,-1.444869 -2.77227,-1.444869 0,0 1.30939,3.550006 1.60951,4.264295 0.69542,1.644664 -0.38158,3.063809 -0.83262,4.642447 -0.29069,1.04185 2.13772,0.8129 2.26463,1.782721 0.18179,1.432007 -4.15197,1.936211 -6.59152,2.417263 -3.65634,0.715146 -7.91635,2.082842 -11.56925,0.884072 -4.3046,-1.383131 -7.37269,-4.12967 -10.46566,-7.235496 1.43801,6.725289 5.4382,10.602857 5.6157,11.422617 0.18607,0.905508 -0.45961,1.091583 -1.04099,1.682394 -1.28967,1.265654 -6.9156603,7.731122 -6.9336603,9.781382 1.6137903,-0.24782 3.5611503,-1.66096 4.9803003,-2.48586 1.58035,-0.90551 7.60593,-5.37303 9.29347,-6.065025 0.38587,-0.160351 5.0549,-1.531476 5.09434,-0.932948 0.0695,0.932948 -0.30784,1.137031 -0.18436,1.527183 0.22638,0.74602 1.44144,1.46546 2.02282,1.98509 1.50918,1.29224 3.21044,2.42841 4.27373,4.15625 1.49203,2.40183 1.55805,4.999171 1.98251,7.677111 0.99469,-0.11148 2.0091,-2.17545 2.55961,-2.99264 0.51278,-0.7726 2.38639,-4.071361 3.09725,-4.275441 0.67227,-0.20408 2.75511,0.95867 3.50284,1.18076 2.85973,0.84806 5.644,1.35398 8.560317,1.35398 3.50799,0.009 12.726,0.2581 19.55505,-4.80023 0.75545,-0.56766 2.55703,-2.7311 2.55703,-2.7311 0,0 -0.37644,-0.57709 -1.04785,-0.79061 0.89779,-0.58481 1.8659,-1.211632 1.94993,-1.92592 z"
id="fish4" />
<path
id="fish5"
d="m 127.65312,60.900973 c 0.0489,-0.44418 -0.2178,-0.896934 -1.01784,-1.415715 -0.72801,-0.47505 -1.4826,-0.932949 -2.2149,-1.401138 -1.6035,-1.02813 -3.29018,-1.969653 -4.89799,-3.079245 -4.67074,-3.24131 -10.22127,-4.404923 -15.76322,-5.150939 -2.27235,-0.286401 -4.812228,-0.168925 -6.721858,-1.574351 -1.48174,-1.081294 -4.04993,-4.828523 -6.86506,-6.456038 -0.4862,-0.290688 -2.77227,-1.444869 -2.77227,-1.444869 0,0 1.30939,3.550006 1.60951,4.264295 0.69542,1.644664 -0.38158,3.063809 -0.83262,4.642447 -0.29069,1.04185 2.13772,0.8129 2.26463,1.782721 0.18179,1.432007 -4.15197,1.936211 -6.59152,2.417263 -3.65634,0.715146 -7.91635,2.082842 -11.56925,0.884072 -4.3046,-1.383131 -7.37269,-4.12967 -10.46566,-7.235496 1.43801,6.725289 5.4382,10.602857 5.6157,11.422617 0.18607,0.905508 -0.45961,1.091583 -1.04099,1.682394 -1.28967,1.265654 -6.91566,7.731125 -6.93366,9.781382 1.61379,-0.247815 3.56115,-1.660957 4.9803,-2.485862 1.58035,-0.905509 7.60593,-5.373029 9.29347,-6.065023 0.38587,-0.160351 5.0549,-1.531476 5.09434,-0.932948 0.0695,0.932948 -0.30784,1.137031 -0.18436,1.527188 0.22638,0.746016 1.44144,1.46545 2.02282,1.985088 1.50918,1.292237 3.21044,2.42841 4.27373,4.156252 1.49203,2.401827 1.55805,4.999163 1.98251,7.677102 0.99469,-0.111473 2.0091,-2.17545 2.55961,-2.992638 0.51278,-0.772598 2.38639,-4.071359 3.09725,-4.275442 0.67227,-0.204082 2.75511,0.958673 3.50284,1.180763 2.85973,0.848057 5.643998,1.353976 8.560318,1.353976 3.50799,0.0094 12.726,0.258104 19.55506,-4.800226 0.75545,-0.567658 2.55703,-2.731104 2.55703,-2.731104 0,0 -0.37644,-0.57709 -1.04785,-0.790605 0.89779,-0.584808 1.8659,-1.211633 1.94993,-1.925921 z"
style="fill:#528484;fill-opacity:1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccc" />
<path
d="m 68.19699,20.522295 c 0.0489,-0.44418 -0.2178,-0.896934 -1.01784,-1.415715 -0.72801,-0.47505 -1.4826,-0.932949 -2.2149,-1.401138 -1.6035,-1.02813 -3.29018,-1.969653 -4.89798,-3.079245 C 55.39553,11.384887 49.845,10.221274 44.30305,9.4752582 42.0307,9.1888572 39.49082,9.3063332 37.58119,7.900907 36.09945,6.819613 33.53126,3.072384 30.71613,1.444869 30.22993,1.154181 27.94386,0 27.94386,0 c 0,0 1.30939,3.550006 1.60951,4.264295 0.69542,1.644664 -0.38158,3.063809 -0.83262,4.642447 -0.29069,1.0418502 2.13772,0.8129002 2.26463,1.782721 0.18179,1.432007 -4.15197,1.936211 -6.59152,2.417263 -3.65634,0.715146 -7.91635,2.082842 -11.56925,0.884072 C 8.52001,12.607667 5.45192,9.8611282 2.35895,6.755302 3.79696,13.480591 7.79715,17.358159 7.97465,18.177919 8.16072,19.083427 7.51504,19.269502 6.93366,19.860313 5.64399,21.125967 0.018,27.591438 0,29.641695 1.61379,29.39388 3.56115,27.980738 4.9803,27.155833 c 1.58035,-0.905509 7.60593,-5.373029 9.29347,-6.065023 0.38587,-0.160351 5.0549,-1.531476 5.09434,-0.932948 0.0695,0.932948 -0.30784,1.137031 -0.18436,1.527188 0.22638,0.746016 1.44144,1.46545 2.02282,1.985088 1.50918,1.292237 3.21044,2.42841 4.27373,4.156252 1.49203,2.401827 1.55805,4.999163 1.98251,7.677102 0.99469,-0.111473 2.0091,-2.17545 2.55961,-2.992638 0.51278,-0.772598 2.38639,-4.071359 3.09725,-4.275442 0.67227,-0.204082 2.75511,0.958673 3.50284,1.180763 2.85973,0.848057 5.644,1.353976 8.56032,1.353976 3.50799,0.0094 12.726,0.258104 19.55505,-4.800226 0.75545,-0.567658 2.55703,-2.731104 2.55703,-2.731104 0,0 -0.37644,-0.57709 -1.04785,-0.790605 0.89779,-0.584808 1.8659,-1.211633 1.94993,-1.925921 z"
id="fish1" />
</svg>
</div>

View file

@ -1,122 +0,0 @@
svg#fish {
}
/* Fish Animation */
svg.fish{
overflow:visible;
}
@-webkit-keyframes swim
{
0% {left: -235px}
90% {left: calc(100% - 235px); width: 235px;}
100% {left: calc(100%); width: 0}
}
@keyframes swim
{
0% {left: -235px}
70% {left: calc(100% - 235px); width: 235px;}
100% {left: calc(100%); width: 0}
}
.fish{
width: 235px;
height: 104px;
left: -235px;
position: absolute;
opacity: 0.4;
animation: swim 20s;
-webkit-animation: swim 20s;
animation-iteration-count: infinite;
-webkit-animation-iteration-count: infinite;
animation-timing-function: linear;
-webkit-animation-timing-function: linear;
}
svg #fish1,
svg #fish2,
svg #fish3,
svg #fish4,
svg #fish5,
svg #fish6 {
fill:#528484;
-moz-animation: bounce 2s infinite;
-webkit-animation: bounce 2s infinite;
animation: bounce 2s infinite;
}
svg #fish2{
animation-delay: 0.5s;
-webkit-animation-delay: 0.5s;
}
svg #fish3{
animation-delay: 0.2s;
-webkit-animation-delay: 0.2s;
}
svg #fish4{
animation-delay: 0.4s;
-webkit-animation-delay: 0.4s;
}
svg #fish5{
animation-delay: 0.1s;
-webkit-animation-delay: 0.1s;
}
svg #fish6{
animation-delay: 0.3s;
-webkit-animation-delay: 0.3s;
}
/**/
@-moz-keyframes bounce {
0%, 50%, 100% {
-moz-transform: translateY(0);
transform: translateY(0);
}
25% {
-moz-transform: translateY(-5px);
transform: translateY(-5px);
}
75% {
-moz-transform: translateY(-3px);
transform: translateY(-3px);
}
}
@-webkit-keyframes bounce {
0%, 50%, 100% {
-webkit-transform: translateY(0);
transform: translateY(0);
}
25% {
-webkit-transform: translateY(-5px);
transform: translateY(-5px);
}
75% {
-webkit-transform: translateY(-3px);
transform: translateY(-3px);
}
}
@keyframes bounce {
0%, 50%, 100% {
-moz-transform: translateY(0);
-ms-transform: translateY(0);
-webkit-transform: translateY(0);
transform: translateY(0);
}
25% {
-moz-transform: translateY(-5px);
-ms-transform: translateY(-5px);
-webkit-transform: translateY(-5px);
transform: translateY(-5px);
}
75% {
-moz-transform: translateY(-3px);
-ms-transform: translateY(-3px);
-webkit-transform: translateY(-3px);
transform: translateY(-3px);
}
}

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FishComponent } from './fish.component';
describe('FishComponent', () => {
let component: FishComponent;
let fixture: ComponentFixture<FishComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FishComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FishComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,12 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'iov-fish',
standalone: true,
imports: [],
templateUrl: './fish.component.html',
styleUrl: './fish.component.scss'
})
export class FishComponent {
}

View file

@ -1,43 +0,0 @@
<div class="bottom-0 bg-secondary pt-12 w-full h-full grid grid-cols-1 content-end justify-end bg-slate-100 gap-8">
<iov-fish></iov-fish>
<iov-card [ctaUrl]="'mailto:thisloke@gmail.com'"
[borderRounded]="false"
class="mr-8 ml-8 relative -top-12 hidden">
<span left>
<span class="text-accent"> Talk is cheap</span>
</span>
<span center>
Interested in working together? <br/> We should schedule a <b class="text-accent">time to chat</b>. <br/>Ill bring the tea :)
</span>
<span right>
Let's Talk!
</span>
</iov-card>
<div class="flex justify-between self-end flex-col-reverse sm:flex-row">
<div class="text-center sm:text-left text-white p-0 sm:p-4 leading-none sm:leading-tight flex flex-col z-10 text-sm sm:text-sm justify-end items-center sm:items-start">
<div class="flex my-2 flex-wrap w-max">
<div class="mx-2">
<span class="italic mr-1">Lorenzo Iovino</span> |
</div>
<a href="https://www.iubenda.com/privacy-policy/98687046" class="hover:text-accent iubenda-white iubenda-noiframe iubenda-embed iubenda-noiframe" title="Privacy Policy ">Privacy Policy</a><script type="text/javascript">(function (w,d) {var loader = function () {var s = d.createElement("script"), tag = d.getElementsByTagName("script")[0]; s.src="https://cdn.iubenda.com/iubenda.js"; tag.parentNode.insertBefore(s,tag);}; if(w.addEventListener){w.addEventListener("load", loader, false);}else if(w.attachEvent){w.attachEvent("onload", loader);}else{w.onload = loader;}})(window, document);</script>
<a href="https://www.iubenda.com/privacy-policy/98687046/cookie-policy" class="ml-2 hover:text-accent iubenda-white iubenda-noiframe iubenda-embed iubenda-noiframe " title="Cookie Policy ">Cookie Policy</a><script type="text/javascript">(function (w,d) {var loader = function () {var s = d.createElement("script"), tag = d.getElementsByTagName("script")[0]; s.src="https://cdn.iubenda.com/iubenda.js"; tag.parentNode.insertBefore(s,tag);}; if(w.addEventListener){w.addEventListener("load", loader, false);}else if(w.attachEvent){w.attachEvent("onload", loader);}else{w.onload = loader;}})(window, document);</script>
</div>
</div>
<div class="text-center sm:text-right text-white p-0 sm:p-4 leading-none sm:leading-tight relative mx-2 text-sm sm:text-sm pb-4 sm:mb-0">
<div class="">Made with <span class="hidden sm:inline">Angular</span>
<a href="https://angular.io/">
<img class="h-6 w-6 mx-1 inline-grid" ngSrc="/assets/angular.svg" height="16" width="16" loading="lazy" alt="Angular Logo">
</a>
and <span class="hidden sm:inline">TailwindCSS</span>
<a href="https://tailwindcss.com/">
<img class="h-6 w-6 mx-1 inline-grid" ngSrc="/assets/tailwind.svg" height="16" width="16" loading="lazy" alt="Tailwind Logo">
</a>
<br class=""/>
<a href="https://github.com/thisloke/loreiov.com" class="hover:text-accent">
Check the source code <img ngSrc="/assets/github.svg" class="h-6 w-6 mx-1 my-1 inline-block" height="800"
width="800" loading="lazy" alt="Github Logo"></a>
</div>
</div>
</div>
</div>

View file

@ -1,5 +0,0 @@
:host-context {
position: relative;
width: 100%;
display: block;
}

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FooterComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,19 +0,0 @@
import { Component } from '@angular/core';
import {FishComponent} from "../fish/fish.component";
import {CardCtaComponent} from "../card-cta/card-cta.component";
import {NgOptimizedImage} from "@angular/common";
@Component({
selector: 'iov-footer',
standalone: true,
imports: [
CardCtaComponent,
FishComponent,
NgOptimizedImage
],
templateUrl: './footer.component.html',
styleUrl: './footer.component.scss'
})
export class FooterComponent {
}

View file

@ -1,52 +0,0 @@
<div class="bg-secondary">
<div class="relative px-6 lg:px-8">
<div class="mx-auto max-w-6xl">
<div class="flex rounded-2xl text-base leading-6 text-gray-600 border-l-8 border-primary hover:ring-gray-900/20 overflow-hidden bg-white items-end">
<iov-ascii-photo [index]="1"
id="asciiPhoto"
class="hidden sm:block absolute text-[5px] rounded-x-2xl leading-none tracking-tighter overflow-hidden"></iov-ascii-photo>
<img ngSrc="/assets/photos/me.png"
id="originalPhoto"
priority="true"
rel="preload"
class="h-[279px] hidden sm:block "
alt="Photo of me" height="300" width="300">
<div class="self-center pt-6 sm:pt-0 mx-2 sm:mx-0 ">
<h1 class="text-4xl font-bold text-gray-900 sm:text-5xl sm:py-8 sm:top-8 mt-4 sm:mt-0 mb-2 sm:mb-0 break-words block sm:hidden mx-2">Hey, I'm Lorenzo!</h1>
<div class="flex justify-center">
<img ngSrc="/assets/photos/me.png"
priority="true"
rel="preload"
class="sm:hidden block clear-both"
alt="Photo of me" height="300" width="300">
</div>
<h1 class="text-4xl font-bold text-gray-900 sm:text-5xl sm:py-8 sm:top-8 mt-4 sm:mt-0 mb-2 sm:mb-0 break-words hidden sm:block">Hey, I'm Lorenzo!</h1>
<div class="mx-auto sm:mr-8">
<h2 class="text-lg mr-4 ml-4 sm:ml-0 sm:mx-4 sm:text-xl leading-2 sm:mr-24 mt-4 sm:mt-0 text-left">
I'm on a quest to uncover life's meaning while diving deep into my passion for Computer Science as a
<span class="text-primary font-bold drop-shadow-sm">Software Engineer</span>.
<br>Here, on my personal website, I share my projects and occasional thoughts.
</h2>
<div class="text-lg mt-4 ml-4 sm:ml-0 mr-4 sm:mx-4 sm:text-xl leading-2 sm:mr-24 text-left">
Explore more about me and feel free to reach out for any questions or collaborations via
<a class="cursor-pointer underline text-primary hover:text-accent drop-shadow-sm" href="mailto:thisloke@gmail.com">email</a>
or on socials!
<div>
</div>
</div>
<div class="flex justify-center sm:justify-end h-[60px] mt-4">
<a class="mx-2 relative cursor-pointer" href="https://www.linkedin.com/in/lorenzoiovino/">
<img ngSrc="/assets/linkedin.svg" class="h-10 sm:h-8 inline-block" height="30" width="30" loading="lazy" alt="Linkedin Logo">
</a>
<a class="mx-2 relative cursor-pointer" href="
https://github.com/thisloke">
<img ngSrc="/assets/github.svg" class="h-10 sm:h-8 inline-block" height="30" width="30" loading="lazy" alt="Github Logo">
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,28 +0,0 @@
@keyframes blink {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes blink-inverted {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
#asciiPhoto {
opacity: 0;
animation: blink-inverted 2s 1;
}
#originalPhoto {
opacity: 1;
animation: blink 2s 1;
}

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroComponent } from './hero.component';
describe('HeroComponent', () => {
let component: HeroComponent;
let fixture: ComponentFixture<HeroComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HeroComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HeroComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,23 +0,0 @@
import {Component} from '@angular/core';
import {AsciiPhotoComponent} from "../ascii-photo/ascii-photo.component";
import {AsyncPipe, NgClass, NgIf, NgOptimizedImage} from "@angular/common";
import {Ng2FittextModule} from "ng2-fittext";
@Component({
selector: 'iov-hero',
standalone: true,
imports: [
AsciiPhotoComponent,
NgIf,
NgClass,
AsyncPipe,
NgOptimizedImage,
Ng2FittextModule,
],
templateUrl: './hero.component.html',
styleUrl: './hero.component.scss'
})
export class HeroComponent {
constructor() {
}
}

View file

@ -1,14 +0,0 @@
<iov-section [noHeight]="true">
<h2 class="text-2xl font-bold my-8" *ngIf="posts.length > 0">Latest blog post</h2>
<div class="grid grid-cols-3 justify-items-center bg-slate-100 gap-8">
<div *ngFor="let post of posts.slice(0,3)">
<iov-post-card [post]="post">
</iov-post-card>
</div>
<a class="font-bold text-primary ring-secondary border-l-8 px-2" routerLink="/blog">View all posts</a>
</div>
<div class="text-center" *ngIf="posts.length === 0">
<p class="text-base">No posts yet.</p>
<br>
</div>
</iov-section>

View file

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HighlightComponent } from './highlight.component';
describe('HighlightComponent', () => {
let component: HighlightComponent;
let fixture: ComponentFixture<HighlightComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HighlightComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HighlightComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,68 +0,0 @@
import { Component } from '@angular/core';
import {SectionComponent} from "../section/section.component";
import {Post} from "../models/post";
import {PostCardComponent} from "../post-card/post-card.component";
import {NgForOf, NgIf} from "@angular/common";
import {RouterLink} from "@angular/router";
@Component({
selector: 'iov-highlight',
standalone: true,
imports: [
SectionComponent,
PostCardComponent,
NgForOf,
NgIf,
RouterLink
],
templateUrl: './highlight.component.html',
styleUrl: './highlight.component.scss'
})
export class HighlightComponent {
posts: Post[] = [
{
title: 'Post 1',
description: 'Description 1',
content: 'Content 1',
date: new Date(),
readTime: 1,
slug: 'post-1',
image: 'https://picsum.photos/300/200'
},
{
title: 'Post 1',
description: 'Description 1',
content: 'Content 1',
date: new Date(),
readTime: 1,
slug: 'post-1',
image: 'https://picsum.photos/300/200'
},
{
title: 'Post 1',
description: 'Description 1',
content: 'Content 1',
date: new Date(),
readTime: 1,
slug: 'post-1',
image: 'https://picsum.photos/300/200'
},{
title: 'Post 1',
description: 'Description 1',
content: 'Content 1',
date: new Date(),
readTime: 1,
slug: 'post-1',
image: 'https://picsum.photos/300/200'
},{
title: 'Post 1',
description: 'Description 1',
content: 'Content 1',
date: new Date(),
readTime: 1,
slug: 'post-1',
image: 'https://picsum.photos/300/200'
}
]
}

Some files were not shown because too many files have changed in this diff Show more