Browse Source

Merge branch 'develop'

hotfix/class_typo
Dslak 5 years ago
parent
commit
5ce90dec5e
  1. 4
      .gitignore
  2. 5
      angular.json
  3. 27
      auth/identity.pem
  4. 5
      deploy.sh
  5. 7
      package.json
  6. 41
      src/apis/auth.php
  7. 16
      src/apis/conn.conn
  8. 80
      src/apis/exhibition.php
  9. 99
      src/apis/index.php
  10. 24
      src/apis/remove.php
  11. 35
      src/apis/upload.php
  12. 75
      src/apis/work.php
  13. 42
      src/app/about/about.component.html
  14. 86
      src/app/about/about.component.scss
  15. 25
      src/app/about/about.component.spec.ts
  16. 21
      src/app/about/about.component.ts
  17. 191
      src/app/admin/admin.component.html
  18. 210
      src/app/admin/admin.component.scss
  19. 25
      src/app/admin/admin.component.spec.ts
  20. 437
      src/app/admin/admin.component.ts
  21. 9
      src/app/app-layout/app-layout.component.html
  22. 3
      src/app/app-layout/app-layout.component.scss
  23. 95
      src/app/app-layout/app-layout.component.ts
  24. 28
      src/app/app-routing.module.ts
  25. 26
      src/app/app.module.ts
  26. 76
      src/app/detail/detail.component.html
  27. 152
      src/app/detail/detail.component.scss
  28. 25
      src/app/detail/detail.component.spec.ts
  29. 184
      src/app/detail/detail.component.ts
  30. 20
      src/app/header/header.component.html
  31. 154
      src/app/header/header.component.scss
  32. 37
      src/app/header/header.component.ts
  33. 31
      src/app/home/home.component.html
  34. 185
      src/app/home/home.component.scss
  35. 25
      src/app/home/home.component.spec.ts
  36. 129
      src/app/home/home.component.ts
  37. 29
      src/app/portfolio/portfolio.component.html
  38. 134
      src/app/portfolio/portfolio.component.scss
  39. 25
      src/app/portfolio/portfolio.component.spec.ts
  40. 84
      src/app/portfolio/portfolio.component.ts
  41. 16
      src/app/services/apis.service.spec.ts
  42. 77
      src/app/services/apis.service.ts
  43. 16
      src/app/services/auth.service.spec.ts
  44. 33
      src/app/services/auth.service.ts
  45. 16
      src/app/services/base-service.ts
  46. 13
      src/app/services/parse-xml.ts
  47. 2
      src/app/spinner/spinner.component.html
  48. 15
      src/app/spinner/spinner.component.scss
  49. 56
      src/app/spinner/spinner.component.ts
  50. BIN
      src/assets/fonts/icomoon.eot
  51. 38
      src/assets/fonts/icomoon.svg
  52. BIN
      src/assets/fonts/icomoon.ttf
  53. BIN
      src/assets/fonts/icomoon.woff
  54. 1
      src/assets/fonts/selection.json
  55. 7
      src/assets/images/angle-down.svg
  56. BIN
      src/assets/images/favicon.png
  57. 11
      src/assets/images/loader.svg
  58. BIN
      src/assets/images/loader.webp
  59. 109
      src/assets/images/logo.svg
  60. 10
      src/assets/scss/fonts.scss
  61. 90
      src/assets/scss/forms.scss
  62. 39
      src/assets/scss/global.scss
  63. 114
      src/assets/scss/icons.scss
  64. 6
      src/assets/scss/main.scss
  65. 36
      src/assets/scss/variables.scss
  66. 52
      src/config/config.ts
  67. 6
      src/environments/environment.prod.ts
  68. 19
      src/environments/environment.ts
  69. BIN
      src/favicon.ico
  70. BIN
      src/favicon.png
  71. 4
      src/index.html
  72. 2
      src/main.ts

4
.gitignore

@ -2,3 +2,7 @@
node_modules/
package-lock\.json
src/assets/images/contents/
dist/

5
angular.json

@ -24,8 +24,9 @@
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
"src/assets/images/favicon.png",
"src/assets",
"src/apis"
],
"styles": [
"src/assets/scss/main.scss"

27
auth/identity.pem

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApNcN8cn0872DhGSdPobrIR9kBfd+qSL/Sqrgk3mywEuuzhN3
MOYbaejwdIJacC0OjCMVgm3f2TziefYd2ssvAUjT+M9FCESUSSDHgPVxt22UDZxz
ZraasA+jdVW0QQtBv8AjyDCXAmMZxcGT1x1htKsKfG2JDxxc1DXub1BaBSkrqcZ1
1u50UK0DBbRcUhd85z9Oivju3IktDaVow09fpwUR/tK1xO8PPsaAUQMKdN1ArGA/
O+FFXAirQ7OD8tT4gCmJYrU2h4mAlcjKwpvmXbtHGmZ2tn4nJsaYUxL0vMPHWoTl
ymOkFbKujBDkTSdKgUdTBy7Bda9ZzAcEBJoGgQIDAQABAoIBAEGVmuO3obEUlu4n
BfUpwwVzst044nkzBnXT1PR4OCmQMyWk0whulTunRXxlnMwC8UXKc7VoN+b79XPm
+2vg6XvOWSAmf2XRu1n5I8doYG1FuOFNfRDB2HvyTOvTRJuYeflr3hC5XGvDuC5Z
XZP6CbTTVKG7BwwvEbQRHSPGyXpBiqd+OOfTpeoobrkGJJAKxvXHO0G7zgB5h/H+
0fjSWvo4QxzOAF74f0qUmWBbvLjWL3yZhVE8mxNcVoV1HlIdXV5anMe9FURci5D4
U8ehGky3sbHjcc7wro5IPWJYTrDAmvhI8wMMx7gbHe2HOzyWRX++ifjfP77kw2e3
IYnwMAECgYEA05Ehmsipk4rmJeCgmDJomQVsJgMW1zjOJwIljSZ9o55+LHpma6r0
O6sdQYgiNwQT0f//iJZkatpDAyC3U6EU9RY7Yoc0R3XK86vryKQlB/Nh1boLldt6
9TGdL5eCzaraiYuLjGvXMGTS8LjB0ftFCGQswl2kcuOFx1YjE4WPoUECgYEAx3Wo
yenPXHCdD4kIrQ9SxPha2aobh33MjdwSYSiDliaEoBg/voWJExFmMJuvhR46xqmy
i8E0RuzzBrG4Zhbdo4kcpdF2GtAuxNw8uYvG+SQub8zAmBT7YpXzTmpGQ2TR3i8S
XzD57s7C1mSkBIwWuVJYcx3Kgm1mQNIOELU31UECgYEAhNc14HhqcaffRp06eRX9
s0dCVsPNzalvV/LzHSOz886KrubT9HrNC8Ivhnwx75Vx1IQHMP4tYyJUvVwHgE0+
WX1yIDWAz/XYTxP94meekNVy8r30lE3RcK+MYNujV/wVaBPktXDpFwvXnyqDGJPL
Dq/HouslXLYbw8QEFjfgrYECgYB+oSU6ozThpCEihsY6ULskj+PlwohdubEO8wO8
KSN5RRT4Ksz1YQPIVkiBXaXOJoX8MCpJbayJxs73lgbS0Xt+4oKMh3GqzjaTBpuK
1MHK1Hyiv+QZ6WA7k6V3SCM5kB1pKItKYeabBStPP2+d725R04SR+PzjVx8O0gzZ
8KL0wQKBgC7LRztq8PMDGTHGXVxWXwZ08ZAf2MAsBvYyAU5xudS+pRqlNGDgyxiv
YQEe9n2DVu7GVsoNnKIapmXV1qx0vTU/CpKV0cpXi6m3XVnbtjBsaYtkYNNZfHVl
E++gP4qgvALKugnzaGn5oby0PPcFFhnNqRwFb5c3diihwMQhkudG
-----END RSA PRIVATE KEY-----

5
deploy.sh

@ -0,0 +1,5 @@
#!/bin/bash
yarn prod
rsync -avz --delete --exclude '/apis/conn.conn' --exclude '.well-known' --exclude '/uploads' -e "ssh -i ./auth/identity.pem -p2222" ./dist/dslak-website/* cdr@2.238.194.8:/www/dslak.it/

7
package.json

@ -6,6 +6,8 @@
"start": "ng serve",
"dev": "ng serve",
"build": "ng build",
"prod": "ng build --prod --configuration production",
"deploy": "sh ./deploy.sh",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
@ -20,6 +22,11 @@
"@angular/platform-browser": "~9.1.7",
"@angular/platform-browser-dynamic": "~9.1.7",
"@angular/router": "~9.1.7",
"@kolkov/angular-editor": "^1.1.4",
"bootstrap": "^4.5.3",
"hammerjs": "^2.0.8",
"ng-particles": "^2.1.11",
"ngx-image-gallery": "^2.0.5",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"

41
src/apis/auth.php

@ -0,0 +1,41 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
$content->status = 200;
$data = json_decode(file_get_contents("php://input"));
if(isset($_GET['act']) && $_GET['act'] == 'login') {
if(isset($data->usr) && $data->usr == 'admin' && isset($data->pwd) && $data->pwd == 'JohnHolmes') {
http_response_code(200);
$content->status = 200;
$content->authToken = base64_encode('admin:JohnHolmes'.date("Y-m-d"));
} else {
http_response_code(401);
$content->status = 401;
$content->action = 'login';
}
} else if(isset($_GET['act']) && $_GET['act'] == 'check') {
if(isset($data->token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
http_response_code(200);
$content->status = 200;
$content->authToken = base64_encode('admin:JohnHolmes'.date("Y-m-d"));
} else {
http_response_code(200);
$content->status = 401;
$content->action = 'check';
$content->token = $data->token;
}
}
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
echo json_encode($content);
?>

16
src/apis/conn.conn

@ -0,0 +1,16 @@
<?php
$DATAhst="localhost";
$DATAusr="root";
$DATApwd="root";
$DATAdtb="www_dslak_it";
/*
$DATAhst="localhost";
$DATAusr="token";
$DATApwd="tokendbpwd";
$DATAdtb="www_dslak_it";
*/
?>

80
src/apis/exhibition.php

@ -0,0 +1,80 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
$data = json_decode(file_get_contents("php://input"));
if(isset($data->token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
if(isset($_GET['act'])) {
if($_GET['act'] == 'save') {
if(isset($data->id)) {
$q = mysqli_query($conn,"UPDATE `exhibitions` SET title = '".addslashes($data->title)."', content = '".addslashes($data->content)."',
tags = '".$data->tags."', date_from = '".$data->date_from."', date_to = '".$data->date_to."',
image = '".$data->image."', works = '".$data->works."', gallery = '".$data->gallery."',
videos = '".$data->videos."' WHERE id = ".$data->id."");
} else {
$q = mysqli_query($conn,"INSERT INTO `exhibitions`
(`id`, `title`, `content`, `tags`, `date_from`, `date_to`, `image`, `works`, `gallery`, `videos`)
VALUES (NULL, '".addslashes($data->title)."', '".addslashes($data->content)."', '".$data->tags."',
'".$data->date_from."', '".$data->date_to."', '".$data->image."', '".$data->works."',
'".$data->gallery."', '".$data->videos."')");
}
if($q) {
http_response_code(201);
$content->status = 201;
} else {
http_response_code(403);
$content->status = "UPDATE `exhibitions` SET title = '".addslashes($data->title)."', content = '".addslashes($data->content)."',
tags = '".$data->tags."', date_from = '".$data->date_from."', date_to = '".$data->date_to."',
image = '".$data->image."', works = '".$data->works."', gallery = '".$data->gallery."',
videos = '".$data->videos."' WHERE id = ".$data->id."";
}
}
if($_GET['act'] == 'delete') {
if(isset($data->id)) {
$q = mysqli_query($conn,"DELETE FROM `exhibitions` WHERE id = ".$data->id."");
if($q) {
http_response_code(201);
$content->status = 201;
} else {
http_response_code(403);
$content->status = 403;
}
}
}
if($q) {
$qe = mysqli_query($conn,"SELECT * FROM `exhibitions` ORDER BY id DESC");
if(mysqli_num_rows($qe) > 0) {
$content->items = array();
while($re = mysqli_fetch_array($qe)) {
$item = null;
$item->id = $re['id'];
$item->title = $re['title'];
$item->date_from = $re['date_from'];
$item->date_to = $re['date_to'];
$item->tags = $re['tags'];
$item->image = $re['image'];
array_push($content->items, $item);
}
}
}
}
} else {
http_response_code(401);
$content->status = 401;
}
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
echo json_encode($content);
?>

99
src/apis/index.php

@ -0,0 +1,99 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
if(isset($_GET['query'])) {
$content->items = array();
switch($_GET['query']) {
case "portfolio":
case "installations":
case "entertainment":
case "performances":
case "workshops":
if($_GET['query'] == 'portfolio') {$filter = '';} else {$filter = "WHERE type='".$_GET['query']."'";}
if($_GET['random']) {$order = 'ORDER BY RAND()';} else {$order = "ORDER BY id DESC";}
$qe = mysqli_query($conn,"SELECT * FROM `works` $filter $order");
if(mysqli_num_rows($qe) > 0) {
$content = null;
$content->items = array();
while($re = mysqli_fetch_array($qe)) {
$item = null;
$item->id = $re['id'];
$item->title = $re['title'];
$item->type = $re['type'];
$item->tags = $re['tags'];
$item->image = $re['image'];
array_push($content->items, $item);
}
}
break;
case "exhibitions":
$qe = mysqli_query($conn,"SELECT * FROM `exhibitions` ORDER BY date_from DESC");
if(mysqli_num_rows($qe) > 0) {
$content = null;
$content->items = array();
while($re = mysqli_fetch_array($qe)) {
$item = null;
$item->id = $re['id'];
$item->title = $re['title'];
$item->date_from = $re['date_from'];
$item->date_to = $re['date_to'];
$item->tags = $re['tags'];
$item->image = $re['image'];
array_push($content->items, $item);
}
}
break;
case "detail":
$qe = mysqli_query($conn,"SELECT * FROM `".$_GET['type']."` WHERE id=".$_GET['id']);
if(mysqli_num_rows($qe)>0) {
$content = null;
$re = mysqli_fetch_array($qe);
$item = null;
$item->id = $re['id'];
$item->title = $re['title'];
$item->content = $re['content'];
$item->tags = $re['tags'];
$item->image = $re['image'];
$item->videos = $re['videos'];
$item->gallery = $re['gallery'];
if($_GET['type'] == 'exhibitions') {
$item->date_from = $re['date_from'];
$item->date_to = $re['date_to'];
$item->works = array();
$qx = mysqli_query($conn,"SELECT id,title FROM `works` WHERE id IN (".$re['works'].")");
while($re = mysqli_fetch_array($qx)) {
$ex = null;
$ex->id = $re['id'];
$ex->title = $re['title'];
array_push($item->works, $ex);
}
} else if($_GET['type'] == 'works') {
$item->type = $re['type'];
$item->exhibitions = array();
$qx = mysqli_query($conn,"SELECT id,title FROM `exhibitions` WHERE id IN (".$re['exhibitions'].")");
while($re = mysqli_fetch_array($qx)) {
$ex = null;
$ex->id = $re['id'];
$ex->title = $re['title'];
array_push($item->exhibitions, $ex);
}
}
$content->item = $item;
}
break;
}
}
http_response_code(200);
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Max-Age: 3600");
echo json_encode($content);
?>

24
src/apis/remove.php

@ -0,0 +1,24 @@
<?php
$content = null;
$data = json_decode(file_get_contents("php://input"));
if(isset($data->token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
@unlink('..'.$data->url);
http_response_code(200);
$content->status = 200;
} else {
http_response_code(401);
$content->status = 401;
}
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
echo json_encode($content);
?>

35
src/apis/upload.php

@ -0,0 +1,35 @@
<?php
$content = null;
if(isset($_POST['token']) && $_POST['token'] == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
if(is_uploaded_file($_FILES['file']['tmp_name'])) {
$file = $_FILES['file']['tmp_name'];
$filename = date("YmdHis").".".end((explode(".", $_FILES["file"]["name"])));
$path = isset($_POST['path']) ? "/uploads/".$_POST['path'] : "/uploads/";
@move_uploaded_file($file, "..".$path."/".$filename);
http_response_code(200);
$content->status = 200;
$content->imageUrl = $path."/".$filename;
} else {
http_response_code(401);
$content->status = 401;
$content->megssage = 'No file uploaded';
}
} else {
http_response_code(401);
$content->status = 401;
}
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
echo json_encode($content);
?>

75
src/apis/work.php

@ -0,0 +1,75 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
$data = json_decode(file_get_contents("php://input"));
if(isset($data->token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
if(isset($_GET['act'])) {
if($_GET['act'] == 'save') {
if(isset($data->id)) {
$q = mysqli_query($conn,"UPDATE `works` SET title = '".addslashes($data->title)."', content = '".addslashes($data->content)."',
type = '".$data->type."', tags = '".$data->tags."', image = '".$data->image."',
exhibitions = '".$data->exhibitions."', gallery = '".$data->gallery."', videos = '".$data->videos."'
WHERE id = ".$data->id."");
} else {
$q = mysqli_query($conn,"INSERT INTO `works` (`id`, `title`, `content`, `type`, `tags`, `image`, `exhibitions`, `gallery`, `videos`)
VALUES (NULL, '".addslashes($data->title)."', '".addslashes($data->content)."', '".$data->type."',
'".$data->tags."', '".$data->image."', '".$data->exhibitions."', '".$data->gallery."',
'".$data->videos."')");
}
if($q) {
http_response_code(201);
$content->status = 201;
} else {
http_response_code(403);
$content->status = 403;
}
}
if($_GET['act'] == 'delete') {
if(isset($data->id)) {
$q = mysqli_query($conn,"DELETE FROM `works` WHERE id = ".$data->id."");
if($q) {
http_response_code(201);
$content->status = 201;
} else {
http_response_code(403);
$content->status = 403;
}
}
}
if($q) {
$qe = mysqli_query($conn,"SELECT * FROM `works` ORDER BY id DESC");
if(mysqli_num_rows($qe) > 0) {
$content->items = array();
while($re = mysqli_fetch_array($qe)) {
$item = null;
$item->id = $re['id'];
$item->title = $re['title'];
$item->type = $re['type'];
$item->tags = $re['tags'];
$item->image = $re['image'];
array_push($content->items, $item);
}
}
}
}
} else {
http_response_code(401);
$content->status = 401;
}
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
echo json_encode($content);
?>

42
src/app/about/about.component.html

@ -0,0 +1,42 @@
<div class="component-about container">
<div class="row">
<div class="col-10 content mx-auto">
<button class="back icon-back" (click)="back()"></button>
<p> <b>DSLAK</b> è un progetto che nasce nel 2010 da un'idea di Carmine De Rosa, sviluppatore ed amministratore di sistema in ambiente Unix/Linux, dopo aver completato gli studi in ambito elettronico approfondisce le proprie conoscenze informatiche presso l'Università di Salerno dove insieme ad un gruppo di appassionati di haking fonda HCSSLUG (Linux Users Group dell'Università di Salerno) e ed il relativo HackLab con il quale realizza numerosi progetti in ambito OpenSource, basati sulla ricerca e sulla sperimentazione in ambito tecnologico. </p>
<p> Nel 2006 si avvicina alle arti digitali e nel 2009, incuriosito dapprima dall'aspetto tecnico ma senza tralasciare quello scenico e concettuale, inizia ad approfondire le propie conoscenze nell'ambito della new-media art e delle installazioni interattive, studiando le soluzioni adottate da artisti affermati e sviluppando varie soluzioni alternative che utilizzano però un approccio più sostenibile, efficiente e soprattutto open. </p>
<p> Nel 2011 realizza le sue prime installazioni interattive e ad oggi è alla continua ricerca di soluzioni creative. </p>
<p> <b>DSLAK</b> ha ad oggi all'attivo numerosi progetti, come workshops sull'utilizzo di sensori e microcontrollori nel campo dell'interattività e delle arti digitali, installazioni interattive e performances live frutto della sperimentazione e della ricerca continua, oltre alle collaborazioni con diversi artisti nazionali ed internazionali. </p>
<div class="about-links pt-4 pb-5">
<a class="link" target="_blank" rel="noopener noreferrer" href="https://www.facebook.com/dslakMediaArts">
<span class="icon icon-facebook"></span>
<div class="label">dslakMediaArts</div>
</a>
<a class="link" target="_blank" rel="noopener noreferrer" href="https://www.youtube.com/c/Dslak">
<span class="icon icon-youtube"></span>
<div class="label">dslak</div>
</a>
<a class="link" target="_blank" rel="noopener noreferrer" href="https://vimeo.com/dslak">
<span class="icon icon-vimeo"></span>
<div class="label">dslak</div>
</a>
<a class="link" target="_blank" rel="noopener noreferrer" href="https://twitter.com/dslak_">
<span class="icon icon-twitter"></span>
<div class="label">dslak_</div>
</a>
<a class="link" target="_blank" rel="noopener noreferrer" href="tel:+393391805849">
<span class="icon icon-phone"></span>
<div class="label">+39 339 1805849</div>
</a>
<a class="link" target="_blank" rel="noopener noreferrer" href="mailto:dslaknma@gmail.com">
<span class="icon icon-envelope"></span>
<div class="label">dslaknma@gmail.com</div>
</a>
</div>
</div>
</div>

86
src/app/about/about.component.scss

@ -0,0 +1,86 @@
@import "../../assets/scss/variables";
.component-about {
.content {
position: relative;
margin: 150px auto 80px auto;
padding: 40px 50px;
font-size: $font-18;
text-align: justify;
background: $white-alpha;
color: $black;
box-shadow: 0px 0px 25px $white-alpha;
border-radius: 10px;
z-index: 1;
.about-links {
color: $black;
.link {
display: flex;
text-decoration: none;
margin: 0;
padding: 0;
line-height: 35px;
width: 200px;
transition: transform .3s;
-webkit-backface-visibility: hidden;
.icon {
display: inline-block;
font-size: 15px;
padding: 5px;
margin: 5px;
background: $dark-gray;
border-radius: 2px;
color: $white;
height: 25px;
width: 25px;
text-align: center;
}
.label {
display: inline-block;
color: $dark-gray;
font-size: $font-16;
padding-left: 5px;
}
&:hover {
transform: scale(1.1);
}
}
}
.back {
position: absolute;
top: -40px;
left: 0px;
height: 40px;
width: 60px;
appearance: none;
border: none;
padding: 0;
font-size: $font-40;
color: $black-alpha;
background: transparent;
cursor: pointer;
transition: transform .3s;
-webkit-backface-visibility: hidden;
&:hover {
transform: scale(1.1) translateX(-10px);
}
}
}
}
@media (min-width: map-get($grid-breakpoints, 'md')) {
.component-about {
.content {
transform: rotate(2deg) skew(0deg, -6deg);
}
}
}

25
src/app/about/about.component.spec.ts

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

21
src/app/about/about.component.ts

@ -0,0 +1,21 @@
import { Component, OnInit } from '@angular/core'
import { Location } from '@angular/common'
@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent implements OnInit {
constructor(
private location: Location
) { }
ngOnInit(): void {
}
back() {
this.location.back()
}
}

191
src/app/admin/admin.component.html

@ -0,0 +1,191 @@
<div class="component-admin">
<div class="row no-gutters" *ngIf="!authCheck">
<div class="col-12 col-md-6 mx-auto">
<form class="login-form-container" (submit)="login()">
<div class="m-2">
<span class="login-label">Username</span>
<input type="text" class="input-text" name="userName" [(ngModel)]="userName">
</div>
<div class="m-2">
<span class="login-label">Password</span>
<input type="password" class="input-text" name="password" [(ngModel)]="password">
</div>
<div class="m-2 pt-4">
<button type="submit" class="button">Sign-in</button>
</div>
</form>
</div>
</div>
<div class="row no-gutters" *ngIf="authCheck">
<div class="col-12 col-md-3 col-lg-2">
<div class="menu">
<span class="section-title">Works</span>
<button class="action" [ngClass]="{'active': activeEditor == 'works-add'}" (click)="showEditor('works-add')">Add</button>
<button class="action" [ngClass]="{'active': activeEditor == 'works-modify'}" (click)="showEditor('works-modify')">Modify</button>
<button class="action" [ngClass]="{'active': activeEditor == 'works-delete'}" (click)="showEditor('works-delete')">Delete</button>
<span class="section-title">Exhibitions</span>
<button class="action" [ngClass]="{'active': activeEditor == 'exhibitions-add'}" (click)="showEditor('exhibitions-add')">Add</button>
<button class="action" [ngClass]="{'active': activeEditor == 'exhibitions-modify'}" (click)="showEditor('exhibitions-modify')">Modify</button>
<button class="action" [ngClass]="{'active': activeEditor == 'exhibitions-delete'}" (click)="showEditor('exhibitions-delete')">Delete</button>
</div>
</div>
<div class="col px-5 py-4">
<div class="edit-container">
<span class="title">{{sectionTitle}}</span>
<form class="form row" *ngIf="activeEditor == 'works-modify' || activeEditor == 'works-delete'">
<div class="col-12">
<select class="input-select" (change)="selectWork($event.target.value)">
<option value="">- Select work from list -</option>
<option value="{{work.id}}" *ngFor="let work of works">
{{work.type}} | {{work.title}}
</option>
</select>
</div>
</form>
<form class="form row" *ngIf="activeEditor == 'exhibitions-modify' || activeEditor == 'exhibitions-delete'">
<div class="col-12">
<select class="input-select" (change)="selectExhibition($event.target.value)">
<option value="">- Select exhibition from list -</option>
<option value="{{exhibition.id}}" *ngFor="let exhibition of exhibitions">
{{exhibition.date_from | date}} | {{exhibition.title}}
</option>
</select>
</div>
</form>
<form class="form row" (submit)="saveData()"
*ngIf="activeEditor == 'works-add' || (activeEditor == 'works-modify' && activeModify) ||
activeEditor == 'exhibitions-add' || (activeEditor == 'exhibitions-modify' && activeModify)">
<div [ngClass]="{'col-8': activeEditor == 'works-add' || activeEditor == 'works-modify',
'col-6': activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'}">
<span class="label">Title</span>
<input type="text" class="input-text" name="title" [(ngModel)]="title">
</div>
<div class="col-4" *ngIf="activeEditor == 'works-add' || activeEditor == 'works-modify'">
<span class="label">Type</span>
<select class="input-select" name="type" [(ngModel)]="type">
<option [value]="sec.section" *ngFor="let sec of workSections">{{sec.title}}</option>
</select>
</div>
<div class="col-3" *ngIf="activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'">
<span class="label">Date from</span>
<input type="date" class="input-text w-100" name="dateFrom" [(ngModel)]="dateFrom" (change)="dateTo = dateFrom">
</div>
<div class="col-3" *ngIf="activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'">
<span class="label">Date to</span>
<input type="date" class="input-text w-100" name="dateTo" [(ngModel)]="dateTo">
</div>
<div class="col-12">
<span class="label">Content</span>
<angular-editor [placeholder]="'Enter text here...'" [config]="editorConfig" name="content" [(ngModel)]="content"></angular-editor>
</div>
<div class="col-12">
<span class="label">Tags</span>
<input type="text" class="input-text" name="tags" [(ngModel)]="tags">
</div>
<div class="col-12">
<span class="label">Gallery</span>
<div class="gallery-container">
<label class="image-add" for="image-add">
<input type="file" id="image-add" (change)="onFileChanged($event)">
</label>
<div class="image-box" [ngClass]="{'main': image.main}" *ngFor="let image of selectedGallery">
<img class="image" [src]="basePath+image.url">
<button type="button" class="remove" (click)="galleryRemove(image.url)"><span class="icon-trash-2"></span></button>
<button type="button" class="set-main" (click)="gallerySetMain(image.url)" *ngIf="!image.main"><span class="icon-check"></span></button>
</div>
</div>
</div>
<div class="col-6" *ngIf="activeEditor == 'works-add' || activeEditor == 'works-modify'">
<span class="label">Exhibitions</span>
<select class="input-select" name="exhibitions" (change)="exhibitionAdd($event.target.value)">
<option value=""></option>
<option value="{{exhibition.id}}" *ngFor="let exhibition of exhibitions">
{{exhibition.date_from | date}} | {{exhibition.title}}
</option>
</select>
<span class="label font-12 pt-2">Selected exhibitions</span>
<span class="selected-exhibition" *ngFor="let se of selectedExhibitions" (click)="exhibitionRemove(se.id)">
{{se.date_from | date}} | {{se.title}}
</span>
</div>
<div class="col-6" *ngIf="activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'">
<span class="label">Works</span>
<select class="input-select" name="works" (change)="workAdd($event.target.value)">
<option value=""></option>
<option value="{{work.id}}" *ngFor="let work of works">
{{work.type}} | {{work.title}}
</option>
</select>
<span class="label font-12 pt-2">Selected works</span>
<span class="selected-work" *ngFor="let sw of selectedWorks" (click)="workRemove(sw.id)">
{{sw.type}} | {{sw.title}}
</span>
</div>
<div class="col-6">
<span class="label">Video</span>
<div class="w-30 d-inline-block pr-2">
<select class="input-select" name="videoType" [(ngModel)]="videoType">
<option value="youtube">YouTube</option>
<option value="vimeo">Vimeo</option>
<option value="embed">Embed</option>
</select>
</div>
<div class="w-60 d-inline-block pr-2">
<input type="text" class="input-text" name="videoURL" [(ngModel)]="videoURL">
</div>
<div class="w-10 d-inline-block">
<span class="button button-transparent icon-plus-square px-0 w-100" (click)="videoAdd()"></span>
</div>
<span class="label font-12 pt-2">Selected Videos</span>
<span class="selected-video" *ngFor="let sv of selectedVideos" (click)="videoRemove(sv.url)">
{{sv.type}} | {{sv.url}}
</span>
</div>
<div class="col-12 pt-5">
<button class="button w-100" type="submit">Save</button>
</div>
</form>
<form class="form row" (submit)="deleteData(modifyId)"
*ngIf="(activeEditor == 'works-delete' || activeEditor == 'exhibitions-delete') && modifyId">
<div class="col-12">
<span class="label">Title</span>
<div class="preview-box" *ngIf="activeEditor == 'works-delete'">{{type}} | {{title}}</div>
<div class="preview-box" *ngIf="activeEditor == 'exhibitions-delete'">{{dateFrom}} | {{title}}</div>
</div>
<div class="col-12">
<span class="label">Content</span>
<div class="preview-box" [innerHTML]="content"></div>
</div>
<div class="col-12">
<span class="label">Gallery</span>
<div class="gallery-container">
<div class="image-box" [ngClass]="{'main': image.main}" *ngFor="let image of selectedGallery">
<img class="image" [src]="basePath+image.url">
<button type="button" class="remove" (click)="galleryRemove(image.url)"><span class="icon-trash-2"></span></button>
</div>
</div>
</div>
<div class="col-12 pt-5">
<button class="button w-100" type="submit">Delete</button>
</div>
</form>
</div>
</div>
</div>
</div>

210
src/app/admin/admin.component.scss

@ -0,0 +1,210 @@
@import "../../assets/scss/variables";
.component-admin {
.login-form-container {
text-align: center;
padding: 30px 40px;
color: $white;
.login-label {
font-size: $font-14;
color: $black;
padding: 8px;
}
.button {
width: 300px;
}
}
.edit-container {
.title {
display: block;
font-size: $font-30;
font-weight: bolder;
text-transform: uppercase;
padding: 20px 0;
}
.form {
.label {
display: block;
font-size: $font-20;
text-transform: uppercase;
padding: 20px 0 5px 0;
}
.gallery-container {
display: flex;
background: $white;
border-radius: 4px;
width: 100%;
padding: 5px;
min-height: 100px;
.image-add {
appearance: none;
display: inline-block;
position: relative;
border: 2px solid $light-gray;
border-radius: 4px;
height: 100px;
width: 100px;
margin: 5px;
cursor: pointer;
&:before {
content: '\e90a';
font-family: $font-icon;
font-size: $font-30;
color: $light-gray;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
input {
visibility: hidden;
}
}
.image-box {
display: inline-block;
position: relative;
border: 2px solid $light-gray;
height: 100px;
width: 120px;
margin: 5px;
border-radius: 4px;
overflow: hidden;
.image {
position: absolute;
height: 100%;
width: 100%;
object-fit: cover;
z-index: 0;
}
.remove,
.set-main {
position: absolute;
top: 4px;
right: 4px;
border: 0;
border-radius: 2px;
color: $black;
height: 20px;
width: 20px;
background: $white-alpha;
padding: 0;
margin: 0;
cursor: pointer;
font-size: $font-12;
z-index: 10;
}
.remove {
color: $red;
}
.set-main {
color: $green;
right: 30px;
}
&.main {
border: 2px solid $yellow;
}
}
}
.selected-exhibition,
.selected-work,
.selected-video {
display: block;
position: relative;
font-size: $font-16;
font-weight: bolder;
border-radius: 4px;
border: 2px solid $white;
background: $white-alpha;
cursor: pointer;
padding: 8px 50px 8px 15px;
margin-bottom: 5px;
&:before {
content: '\e903';
position: absolute;
top: 8px;
right: 10px;
font-family: $font-icon;
font-size: $font-20;
color: $gray;
}
}
.preview-box {
border-radius: 4px;
background: $white-alpha2;
padding: 10px;
width: 100%;
}
}
}
.menu {
background: $dark-gray;
.section-title {
display: block;
width: 100%;
padding: 50px 10px 10px;
font-size: $font-22;
font-weight: bolder;
text-transform: uppercase;
color: $white;
text-align: center;
border-bottom: 1px solid $black-alpha;
}
.action {
display: block;
appearance: none;
border: none;
border-radius: 0px;
width: 100%;
padding: 10px;
font-size: $font-14;
text-transform: uppercase;
color: $white;
background: $dark-gray;
cursor: pointer;
border-bottom: 1px solid $black-alpha;
&.active {
background: $yellow;
color: $black;
border: none;
}
}
}
}
@media (min-width: map-get($grid-breakpoints, 'md')) {
.component-admin {
.menu {
position: fixed;
height: 100vh;
width: 25%;
}
}
}
@media (min-width: map-get($grid-breakpoints, 'lg')) {
.component-admin {
.menu {
width: 16.66%;
}
}
}

25
src/app/admin/admin.component.spec.ts

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

437
src/app/admin/admin.component.ts

@ -0,0 +1,437 @@
import { Component, OnInit } from '@angular/core'
import { ApisService } from '../services/apis.service'
import { AuthService } from '../services/auth.service'
import { AngularEditorConfig } from '@kolkov/angular-editor'
import { environment } from '../../environments/environment'
import { editorConfig } from '../../config/config'
@Component({
selector: 'app-admin',
templateUrl: './admin.component.html',
styleUrls: ['./admin.component.scss']
})
export class AdminComponent implements OnInit {
private restApi = `${environment.API_URL}`
public basePath = `${environment.BASE_PATH}`
public authCheck: boolean = false
public userName: string = ''
public password: string = ''
public activeEditor: string = ''
public sectionTitle: string = ''
public activeModify: boolean = false
public modifyId: number = null
public exhibitions: any = []
public works: any = []
public selectedExhibitions: any = []
public selectedWorks: any = []
public selectedVideos: any = []
public selectedGallery: any = []
// ngModels
public title: string = ''
public type: string = ''
public content: string = ''
public tags: string = ''
public dateFrom: string = ''
public dateTo: string = ''
public mainImage: string = ''
public videoType: string = ''
public videoURL: string = ''
public editorConfig: AngularEditorConfig = editorConfig
public workSections: any = [
{title: 'Entertainment', section: 'entertainment'},
{title: 'Installations', section: 'installations'},
{title: 'Performances', section: 'performances'},
{title: 'Workshops', section: 'workshops'}
]
constructor(
private authService: AuthService,
private apisService: ApisService
) { }
ngOnInit(): void {
const body = { token: window.sessionStorage.getItem('authToken') }
this.authService.authCheck(body).toPromise().then((response) => {
this.authCheck = response.status && response.status == 200
if(this.authCheck) {
this.loadData()
}
},(error) => {
this.authCheck = false
console.error('Auth ERROR INIT', error)
}).catch((e) => {
this.authCheck = false
console.error('Auth CATCH INIT', e)
})
}
login(): void {
const body = { usr: this.userName, pwd: this.password }
this.authService.login(body).toPromise().then((response) => {
this.authCheck = response.status && response.status == 200
if(this.authCheck) {
window.sessionStorage.setItem('authToken', response.authToken)
this.loadData()
}
},(error) => {
console.error('Auth ERROR', error)
}).catch((e) => {
console.error('Auth CATCH', e)
})
}
loadData(): void {
this.apisService.getPortfolio('exhibitions').toPromise().then((response) => {
this.exhibitions = response.items
},(error) => {
console.error('getPortfolio ERROR', error)
}).catch((e) => {
console.error('getPortfolio CATCH', e)
})
this.apisService.getPortfolio('portfolio').toPromise().then((response) => {
this.works = response.items
},(error) => {
console.error('getPortfolio ERROR', error)
}).catch((e) => {
console.error('getPortfolio CATCH', e)
})
}
showEditor(section): void {
switch(section) {
case 'works-add':
this.sectionTitle = 'Add work'
break;
case 'works-modify':
this.sectionTitle = 'Modify work'
break;
case 'works-delete':
this.sectionTitle = 'Delete work'
break;
case 'exhibitions-add':
this.sectionTitle = 'Add exhibition'
break;
case 'exhibitions-modify':
this.sectionTitle = 'Modify exhibition'
break;
case 'exhibitions-delete':
this.sectionTitle = 'Delete exhibition'
break;
}
this.activeModify = false
this.activeEditor = section
this.modifyId = null
this.resetFields()
}
exhibitionAdd(id): void {
this.selectedExhibitions.push(
this.exhibitions.filter(item => item.id == id)[0]
)
this.exhibitions = this.exhibitions.filter(item => item.id != id)
}
exhibitionRemove(id): void {
this.exhibitions.push(
this.selectedExhibitions.filter(item => item.id == id)[0]
)
this.selectedExhibitions = this.selectedExhibitions.filter(item => item.id != id)
}
workAdd(id): void {
this.selectedWorks.push(
this.works.filter(item => item.id == id)[0]
)
this.works = this.works.filter(item => item.id != id)
}
workRemove(id): void {
this.works.push(
this.selectedWorks.filter(item => item.id == id)[0]
)
this.selectedWorks = this.selectedWorks.filter(item => item.id != id)
}
videoAdd(): void {
this.selectedVideos.push({
type: this.videoType,
url: this.videoURL.replace(/\"/g, "\\\"")
})
this.videoURL = ''
}
videoRemove(url): void {
this.selectedVideos = this.selectedVideos.filter(item => item.url != url)
}
onFileChanged(e) {
const file = (<HTMLInputElement>e.target).files[0]
const uploadData = new FormData()
uploadData.append('token', window.sessionStorage.getItem('authToken'))
uploadData.append('path', 'assets')
uploadData.append('file', file, file.name)
this.apisService.uploadImage(uploadData).toPromise().then((response) => {
this.selectedGallery.push({
title: response.title || '',
url: response.imageUrl
})
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
gallerySetMain(url): void {
this.selectedGallery.forEach((e) => {
if(e.url == url) {
e.main = true
} else {
delete e.main
}
})
}
galleryRemove(url): void {
const body = {
token: window.sessionStorage.getItem('authToken'),
url: url
}
this.apisService.removeImage(body).toPromise().then((response) => {
this.selectedGallery = this.selectedGallery.filter(item => item.url != url)
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
selectWork(id): void {
this.activeModify = true
this.modifyId = id
this.apisService.getDetails('works', id).toPromise().then((response) => {
const detail = response.item
this.apisService.getPortfolio('exhibitions').toPromise().then((response) => {
this.exhibitions = response.items
this.title = detail.title
this.content = detail.content
this.type = detail.type
this.tags = detail.tags
this.selectedExhibitions = detail.exhibitions.length ?
this.exhibitions.filter(item => detail.exhibitions.map(a => a.id).indexOf(item.id) > -1) : []
this.selectedGallery = detail.gallery ? JSON.parse(detail.gallery) : []
this.selectedVideos = detail.videos ? JSON.parse(detail.videos) : []
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
selectExhibition(id): void {
this.activeModify = true
this.modifyId = id
this.apisService.getDetails('exhibitions', id).toPromise().then((response) => {
const detail = response.item
this.apisService.getPortfolio('portfolio').toPromise().then((response) => {
this.works = response.items
this.title = detail.title
this.content = detail.content
this.tags = detail.tags
this.dateFrom = detail.date_from
this.dateTo = detail.date_to
this.selectedWorks = detail.works.length ?
this.works.filter(item => detail.works.map(a => a.id).indexOf(item.id) > -1) : []
this.selectedGallery = detail.gallery ? JSON.parse(detail.gallery) : []
this.selectedVideos = detail.videos ? JSON.parse(detail.videos) : []
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
saveData(): void {
if(this.activeEditor == 'works-add' || this.activeEditor == 'works-modify') {
this.saveWork()
}
if(this.activeEditor == 'exhibitions-add' || this.activeEditor == 'exhibitions-modify') {
this.saveExhibition()
}
}
saveWork(): void {
let error = false
let errorMessages = []
const mainImage = this.selectedGallery.filter(item => item.main)
if(!this.title){
error = true
errorMessages.push('No title')
}
if(!this.type){
error = true
errorMessages.push('No type selected')
}
if(this.selectedGallery.length == 0 || mainImage.length == 0){
error = true
errorMessages.push('No main image selected')
}
if(error) {
console.log('ERRORS:',errorMessages)
} else {
const body = {
id: this.activeModify ? this.modifyId : null,
token: window.sessionStorage.getItem('authToken'),
title: this.title,
content: this.content,
type: this.type,
tags: this.tags,
image: mainImage[0].url,
exhibitions: this.selectedExhibitions.map(a => a.id).join(','),
gallery: JSON.stringify(this.selectedGallery),
videos: JSON.stringify(this.selectedVideos)
}
this.apisService.saveWork(body).toPromise().then((response) => {
this.resetFields()
this.works = response.items
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
}
saveExhibition(): void {
let error = false
let errorMessages = []
const mainImage = this.selectedGallery.filter(item => item.main)
if(!this.title){
error = true
errorMessages.push('No title')
}
if(!this.dateFrom){
error = true
errorMessages.push('No date from selected')
}
if(!this.dateTo){
error = true
errorMessages.push('No date to selected')
}
if(this.selectedGallery.length == 0 || mainImage.length == 0){
error = true
errorMessages.push('No main image selected')
}
if(error) {
console.log('ERRORS:',errorMessages)
} else {
const body = {
id: this.activeModify ? this.modifyId : null,
token: window.sessionStorage.getItem('authToken'),
title: this.title,
content: this.content,
date_from: this.dateFrom,
date_to: this.dateTo,
tags: this.tags,
image: mainImage[0].url,
works: this.selectedWorks.map(a => a.id).join(','),
gallery: JSON.stringify(this.selectedGallery),
videos: JSON.stringify(this.selectedVideos)
}
this.apisService.saveExhibition(body).toPromise().then((response) => {
this.resetFields()
this.exhibitions = response.items
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
}
deleteData(id): void {
if(this.activeEditor == 'works-delete') {
this.deleteWork(id)
}
if(this.activeEditor == 'exhibitions-delete') {
this.deleteExhibition(id)
}
}
deleteWork(id): void {
const body = {
id: id,
token: window.sessionStorage.getItem('authToken')
}
this.apisService.deleteWork(body).toPromise().then((response) => {
this.resetFields()
this.works = response.items
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
deleteExhibition(id): void {
const body = {
id: id,
token: window.sessionStorage.getItem('authToken')
}
this.apisService.deleteExhibition(body).toPromise().then((response) => {
this.resetFields()
this.exhibitions = response.items
},(error) => {
console.error(error)
}).catch((e) => {
console.error(e)
})
}
resetFields(): void {
this.title = ''
this.content = ''
this.type = ''
this.tags = ''
this.dateFrom = ''
this.dateTo = ''
this.selectedExhibitions = []
this.selectedWorks = []
this.selectedGallery = []
this.selectedVideos = []
this.modifyId = null
}
}

9
src/app/app-layout/app-layout.component.html

@ -1,2 +1,7 @@
<app-header></app-header>
<p>app-layout works!</p>
<main>
<app-header *ngIf="page != '/admin'"></app-header>
<router-outlet></router-outlet>
<Particles class="particles" *ngIf="particlesEnabled && page != '/admin'"
[id]="id" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)"></Particles>
</main>

3
src/app/app-layout/app-layout.component.scss

@ -0,0 +1,3 @@
main {
overflow: hidden;
}

95
src/app/app-layout/app-layout.component.ts

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'
import type { Container } from 'tsparticles'
@Component({
selector: 'app-app-layout',
@ -7,9 +9,98 @@ import { Component, OnInit } from '@angular/core';
})
export class AppLayoutComponent implements OnInit {
constructor() { }
public page: string = '/'
public particlesEnabled: boolean = false
public id: string = 'tsparticles'
public particlesOptions: any = {
background: {
color: {
value: "transparent"
}
},
fpsLimit: 60,
interactivity: {
detectsOn: "canvas",
events: {
onClick: {
enable: true,
mode: "push"
},
onHover: {
enable: true,
mode: "repulse"
},
resize: true
},
modes: {
bubble: {
distance: 200,
duration: 2,
opacity: 0.8,
size: 40
},
push: {
quantity: 6
},
repulse: {
distance: 100,
duration: 0.4
}
}
},
particles: {
color: {
value: "#fff"
},
links: {
color: "#fff",
distance: 150,
enable: true,
opacity: 0.7,
width: 1
},
collisions: {
enable: true
},
move: {
direction: "none",
enable: true,
outMode: "bounce",
random: false,
speed: 2,
straight: false
},
number: {
density: {
enable: true,
value_area: 600
},
value: 90
},
opacity: {
value: 0.5
},
shape: {
type: "circle"
},
size: {
random: true,
value: 5
}
},
detectRetina: true
}
constructor(private router: Router) { }
ngOnInit(): void {
this.page = this.router.url
this.particlesEnabled = this.page != '/admin'
}
particlesLoaded(container: Container): void {
//console.log('particlesLoaded', container)
}
}

28
src/app/app-routing.module.ts

@ -1,13 +1,37 @@
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
import { AppLayoutComponent } from './app-layout/app-layout.component'
import { AboutComponent } from './about/about.component'
import { PortfolioComponent } from './portfolio/portfolio.component'
import { DetailComponent } from './detail/detail.component'
import { HomeComponent } from './home/home.component'
import { AdminComponent } from './admin/admin.component'
const routes: Routes = [
{
path: '',
component: AppLayoutComponent,
children: []
children: [
//{ path: '', redirectTo: '/portfolio', pathMatch: 'full' },
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'portfolio', component: PortfolioComponent },
{ path: 'exhibitions', component: PortfolioComponent },
{ path: 'installations', component: PortfolioComponent },
{ path: 'entertainment', component: PortfolioComponent },
{ path: 'performances', component: PortfolioComponent },
{ path: 'workshops', component: PortfolioComponent },
{ path: 'detail', component: DetailComponent,
children: [
{ path: '**', component: DetailComponent,
children: [
{ path: '**', component: DetailComponent }
]
}
]
},
{ path: 'admin', component: AdminComponent }
]
}
]

26
src/app/app.module.ts

@ -1,20 +1,42 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { NgParticlesModule } from "ng-particles";
import { NgxImageGalleryModule } from 'ngx-image-gallery';
import { FormsModule } from '@angular/forms';
import { AngularEditorModule } from '@kolkov/angular-editor';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { AppLayoutComponent } from './app-layout/app-layout.component';
import { AboutComponent } from './about/about.component';
import { PortfolioComponent } from './portfolio/portfolio.component';
import { DetailComponent } from './detail/detail.component';
import { AdminComponent } from './admin/admin.component';
import { HomeComponent } from './home/home.component';
import { SpinnerComponent } from './spinner/spinner.component';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
AppLayoutComponent
AppLayoutComponent,
AboutComponent,
PortfolioComponent,
DetailComponent,
AdminComponent,
HomeComponent,
SpinnerComponent
],
imports: [
BrowserModule,
AppRoutingModule
AppRoutingModule,
NgParticlesModule,
NgxImageGalleryModule,
FormsModule,
AngularEditorModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]

76
src/app/detail/detail.component.html

@ -0,0 +1,76 @@
<div class="component-detail container">
<div class="row">
<div class="col-11 col-md-10 col-lg-12 content mx-auto">
<button class="back icon-back" (click)="back()"></button>
<h2 class="title">{{details.title}}</h2>
<div class="date-container" *ngIf="section == 'exhibitions'">
<span class="date-indication" *ngIf="details.date_from != details.date_to">from</span>
<span class="date-indication" *ngIf="details.date_from == details.date_to">on</span>
<span class="date">{{details.date_from | date}}</span>
<span class="date" *ngIf="details.date_from != details.date_to"> <span class="date-indication">to</span> {{details.date_to | date}}</span>
</div>
<div class="row no-gutters gallery" *ngIf="galleryImages.length">
<div class="col-12" *ngFor="let image of galleryImages; let i = index"
[ngClass]="{'col-md-4': galleryImages.length < 4 && (galleryImages.length % 3) == 2,
'col-md-12': galleryImages.length < 4 && (galleryImages.length % 3) == 1,
'col-md-3': galleryImages.length >= 3}">
<div class="gallery-container">
<span class="gallery-title">{{image.title}}</span>
<img class="image" *ngIf="loadedImages[i]" src="/assets/images/loader.svg" alt="loading">
<img class="image" [hidden]="loadedImages[i]" (load)="onLoad(i)" [src]="image.url" (click)="openGallery(i)">
</div>
</div>
</div>
<div class="text" [innerHTML]="details.content"></div>
<div class="row videos" *ngIf="details.videos && details.videos.length">
<div class="col-12" *ngFor="let video of details.videos"
[ngClass]="{'col-md-6': details.videos.length < 4 && (details.videos.length % 3) == 2,
'col-md-12': details.videos.length < 4 && (details.videos.length % 3) == 1,
'col-md-4': details.videos.length >= 3}">
<span class="video-title">{{video.title}}</span>
<div class="youtube" *ngIf="video.type == 'youtube'">
<iframe class="iframe" [src]="video.embed"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
<div class="vimeo" *ngIf="video.type == 'vimeo'">
<iframe class="iframe" title="vimeo-player" [src]="video.embed" src="https://player.vimeo.com/video/104753208"
frameborder="0" allowfullscreen></iframe>
</div>
<div class="embed" *ngIf="video.type == 'embed'" [innerHTML]="video.embed"></div>
</div>
</div>
<span class="tags" *ngIf="details.tags"><b>Tags:</b> {{details.tags}}</span>
<span class="links" *ngIf="details.exhibitions && details.exhibitions.length"><b>Exhibitions:</b>
<span class="link" *ngFor="let exhibition of details.exhibitions"
(click)="showDetails('exhibitions', exhibition.id, exhibition.title)"
routerLink="/detail/exhibitions/{{exhibition.id}}">{{exhibition.title}} </span>
</span>
<span class="links" *ngIf="details.works && details.works.length"><b>Works:</b>
<span class="link" *ngFor="let work of details.works"
(click)="showDetails('works', work.id, work.title)"
routerLink="/detail/works/{{work.id}}">{{work.title}} </span>
</span>
</div>
</div>
<ngx-image-gallery
[images]="galleryImages"
[conf]="conf"
(onOpen)="galleryOpened($event)"
(onClose)="galleryClosed()"
(onImageClicked)="galleryImageClicked($event)"
(onImageChange)="galleryImageChanged($event)"
(onDelete)="deleteImage($event)"
></ngx-image-gallery>
<app-spinner [show]="!loaded"></app-spinner>

152
src/app/detail/detail.component.scss

@ -0,0 +1,152 @@
@import "../../assets/scss/variables";
.component-detail {
.content {
position: relative;
margin: 150px auto 80px auto;
padding: 40px 50px;
background: $white-alpha;
color: $black;
box-shadow: 0px 0px 25px $white-alpha;
border-radius: 10px;
z-index: 1;
.title {
margin: 0;
font-size: $font-34;
font-weight: bold;
text-transform: uppercase;
}
.text {
font-size: $font-18;
text-align: justify;
padding: 40px 0;
}
.date-container {
display: inline-flex;
.date {
display: inline-flex;
margin: auto;
font-size: $font-20;
}
.date-indication {
margin: 2px 5px auto 5px;
font-size: $font-12;
}
}
.gallery {
padding-top: 40px;
.gallery-title {
font-size: $font-18;
font-weight: bold;
padding-bottom: 5px;
}
.gallery-container {
position: relative;
//padding-bottom: 56.25%;
height: 180px;
.image{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
transition: transform .4s;
&:hover {
transform: scale(1.2) rotate(1deg);
z-index: 10;
}
}
}
}
.videos {
padding-bottom: 40px;
.video-title {
font-size: $font-18;
font-weight: bold;
padding-bottom: 5px;
}
.youtube,
.vimeo,
.embed {
position: relative;
padding-bottom: 56.25%;
iframe,
.iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
}
.tags,
.links {
display: block;
font-size: $font-12;
text-transform: uppercase;
padding: 5px 0;
.link {
display: inline-block;
font-size: $font-12;
text-transform: uppercase;
padding: 0 5px;
cursor: pointer;
}
}
.back {
position: absolute;
top: -40px;
left: 0px;
height: 40px;
width: 60px;
appearance: none;
border: none;
padding: 0;
font-size: $font-40;
color: $black-alpha;
background: transparent;
cursor: pointer;
transition: transform .3s;
-webkit-backface-visibility: hidden;
&:hover {
transform: scale(1.1) translateX(-10px);
}
}
}
}
@media (min-width: map-get($grid-breakpoints, 'md')) {
.component-detail {
.content {
transform: rotate(2deg) skew(0deg, -6deg);
.date-container {
position: absolute;
top: 40px;
right: 40px;
}
}
}
}

25
src/app/detail/detail.component.spec.ts

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

184
src/app/detail/detail.component.ts

@ -0,0 +1,184 @@
import { Component, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'
import { Router, NavigationEnd, NavigationStart, ActivatedRoute } from '@angular/router'
import { DomSanitizer } from '@angular/platform-browser'
import { Location } from '@angular/common'
import { NgxImageGalleryComponent, GALLERY_IMAGE, GALLERY_CONF } from "ngx-image-gallery"
import { ApisService } from '../services/apis.service'
import { environment } from '../../environments/environment'
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class DetailComponent implements OnInit {
public basePath = `${environment.BASE_PATH}`
public loaded = false
public init = false
@ViewChild(NgxImageGalleryComponent) ngxImageGallery: NgxImageGalleryComponent
public details: any = {}
public section: string = ''
public id: number = 0
private history: string[] = []
public conf: GALLERY_CONF = {
imageOffset: '0px',
showDeleteControl: false,
showImageTitle: false,
}
public galleryImages: GALLERY_IMAGE[] = []
public loadedImages: boolean[] = []
constructor(
private apisService: ApisService,
private router: Router,
private location: Location,
private activeRoute: ActivatedRoute,
private sanitizer: DomSanitizer
) { }
ngOnInit(): void {
this.section = this.router.url.split('/')[2]
this.id = parseInt(this.router.url.split('/')[3])
this.showDetails(this.section, this.id)
}
ngAfterContentInit() {
this.init = true
}
showDetails(section, id, title = ''): void {
this.galleryImages = []
this.apisService.getDetails(section, id).toPromise().then((response) => {
if(this.history[this.history.length - 1] != `/detail/${section}/${id}`) {
this.history.push(`/detail/${section}/${id}/${title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '_')}`)
}
const detail = response.item
detail.videos = detail.videos ? JSON.parse(detail.videos) : []
detail.videos.forEach((e) => {
e.code = e.url.split('/').pop()
switch(e.type) {
case 'youtube':
e.embed = this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${e.code}`)
break
case 'vimeo':
e.embed = this.sanitizer.bypassSecurityTrustResourceUrl(`https://player.vimeo.com/video/${e.code}`)
break
case 'embed':
e.embed = this.sanitizer.bypassSecurityTrustHtml(e.url)
break
}
})
detail.gallery = detail.gallery ? JSON.parse(detail.gallery) : []
detail.gallery.forEach((e) => {
if(!e.main) {
this.galleryImages.push({
url: `${this.basePath}${e.url}`,
altText: e.title,
title: e.title,
thumbnailUrl: `${this.basePath}${e.url}`
})
this.loadedImages.push(true)
}
})
this.loaded = !this.loadedImages.length
this.details = detail
},(error) => {
console.error('getPortfolio ERROR', error)
}).catch((e) => {
console.error('getPortfolio CATCH', e)
})
}
back(): void {
this.history.pop()
if(this.history.length > 0) {
const last = this.history[this.history.length - 1]
this.section = last.split('/')[2]
this.id = parseInt(last.split('/')[3])
this.showDetails(this.section, this.id)
this.location.back()
} else {
this.location.back()
}
}
onLoad(index): void {
this.loadedImages[index] = false
this.loaded = this.init && this.loadedImages.every( (val) => !val)
}
openGallery(index: number = 0) {
this.ngxImageGallery.open(index)
}
// close gallery
closeGallery() {
this.ngxImageGallery.close()
}
// set new active(visible) image in gallery
newImage(index: number = 0) {
this.ngxImageGallery.setActiveImage(index)
}
// next image in gallery
nextImage(index: number = 0) {
this.ngxImageGallery.next()
}
// prev image in gallery
prevImage(index: number = 0) {
this.ngxImageGallery.prev()
}
/**************************************************/
// EVENTS
// callback on gallery opened
galleryOpened(index) {
console.info('Gallery opened at index ', index)
}
// callback on gallery closed
galleryClosed() {
console.info('Gallery closed.')
}
// callback on gallery image clicked
galleryImageClicked(index) {
console.info('Gallery image clicked with index ', index)
}
// callback on gallery image changed
galleryImageChanged(index) {
console.info('Gallery image changed to index ', index)
}
// callback on user clicked delete button
deleteImage(index) {
console.info('Delete image at index ', index)
}
}

20
src/app/header/header.component.html

@ -1,3 +1,21 @@
<header class="component-header">
<header class="component-header" [ngClass]="{'sticky': isSticky}">
<div class="logo-container" [ngClass]="{'menu-open': isMenuOpen}">
<span class="circles"></span>
<img class="logo" src="assets/images/logo.svg" (click)="toggleMenu()">
</div>
<div class="menu" [ngClass]="{'open': isMenuOpen}">
<nav class="nav">
<ul class="items">
<li class="item" routerLink="/about" [routerLinkActive]="'active'">About</li>
<li class="item" routerLink="/portfolio" [routerLinkActive]="'active'">Portfolio</li>
<li class="item" routerLink="/exhibitions" [routerLinkActive]="'active'">Exhibitions</li>
<li class="item" routerLink="/installations" [routerLinkActive]="'active'">Installations</li>
<li class="item" routerLink="/entertainment" [routerLinkActive]="'active'">Entertainment</li>
<li class="item" routerLink="/performances" [routerLinkActive]="'active'">Performances</li>
<li class="item" routerLink="/workshops" [routerLinkActive]="'active'">Workshops</li>
</ul>
</nav>
</div>
</header>

154
src/app/header/header.component.scss

@ -1,9 +1,157 @@
@import "../../assets/scss/variables";
.component-header {
position: fixed;
display: flex;
top: 0;
left: 0;
height: 5px;
width: 100%;
background: black;
height: 100vh;
width: 100vw;
background: transparent;
transition: height .5s, background .4s;
z-index: 100;
.logo-container {
height: 200px;
width: 200px;
max-height: 50vh;
max-width: 90vw;
position: relative;
margin: auto;
padding-top: 20px;
transition: height .6s, width .6s;
z-index: 101;
.logo {
display: flex;
height: 100%;
width: 100%;
margin: auto;
cursor: pointer;
object-fit: contain;
}
.circles {
&:before,
&:after {
content: ' ';
position: absolute;
top: -12%;
left: -12%;
display: block;
height: 125%;
width: 125%;
background: $white-alpha;
border-radius: 5px;
z-index: -1;
opacity: .3;
transition: background .6s;
}
&:after {
border-radius: 20px;
animation: circle2 13s linear infinite;
opacity: .4;
}
&:before {
border-radius: 20px;
animation: circle1 10s linear infinite;
}
}
}
&.sticky {
height: 0;
background: transparent;
.logo-container {
height: 100px;
width: 100px;
.circles {
&:before,
&:after {
//background: $yellow;
}
}
&.menu-open {
height: 140px;
width: 140px;
.circles {
&:before,
&:after {
background: $white-alpha;
}
}
}
}
}
.menu {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: $yellow-alpha;
border-radius: 100px;
transform: scale(0) skew(20deg, 20deg);
transition: transform .5s, border-radius .4s;
z-index: -1;
.nav {
display: block;
text-align: center;
padding-top: 180px;
height: 100vh;
overflow: hidden;
overflow-y: auto;
.items {
list-style: none;
padding: 0;
margin: 0;
.item {
padding: 0;
margin: 10px 0;
font-weight: bold;
font-size: $font-30;
color: $black;
cursor: pointer;
transition: transform .4s;
&:hover {
transform: scale(1.4);
}
&.active {
text-decoration: underline;
}
}
}
}
&.open {
border-radius: 0;
transform: scale(1) skew(0deg, 0deg);
z-index: 100;
}
}
}
@keyframes circle1 {
0% { transform: translate(-5%, 8%) rotate(10deg) scale(1) skew(20deg, 20deg); }
75% { transform: translate(10%, -5%) rotate(20deg) scale(1.1) skew(-10deg, -20deg); }
50% { transform: translate(5%, 0%) rotate(-16deg) scale(.6) skew(-40deg, -10deg); }
100% { transform: translate(-5%, 8%) rotate(10deg) scale(1) skew(20deg, 20deg); }
}
@keyframes circle2 {
0% { transform: translate(10%, 5%) rotate(-30deg) scale(1) skew(20deg, 20deg); }
50% { transform: translate(-5%, 8%) rotate(-17deg) scale(.7) skew(-10deg, -20deg); }
75% { transform: translate(0%, -10%) rotate(10deg) scale(1.2) skew(-10deg, -50deg); }
100% { transform: translate(10%, 5%) rotate(-30deg) scale(1) skew(20deg, 20deg); }
}

37
src/app/header/header.component.ts

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, HostListener, Inject } from '@angular/core'
import { Router, NavigationEnd } from '@angular/router'
import { DOCUMENT } from '@angular/common'
@Component({
selector: 'app-header',
@ -7,9 +9,40 @@ import { Component, OnInit } from '@angular/core';
})
export class HeaderComponent implements OnInit {
constructor() { }
public isSticky: boolean = true
public isMenuOpen: boolean = false
public isFirstScroll: boolean = true
constructor(@Inject(DOCUMENT) private document: Document, private router: Router) {
router.events.subscribe((val) => {
this.isMenuOpen = false
//this.isSticky = true
this.document.body.classList.remove('no-scroll')
})
}
ngOnInit(): void {
//this.isSticky = this.router.url != '/'
}
@HostListener('window:scroll', ['$event'])
onScroll(event) {
const verticalOffset = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop || 0
this.isFirstScroll = this.router.url == '/'
//this.isSticky = this.isFirstScroll ? this.isMenuOpen || verticalOffset > 10 : true
}
toggleMenu(): void {
this.isMenuOpen = !this.isMenuOpen
if(this.isMenuOpen) {
this.isSticky = true
this.document.body.classList.add('no-scroll')
} else {
this.document.body.classList.remove('no-scroll')
}
}
}

31
src/app/home/home.component.html

@ -0,0 +1,31 @@
<div class="component-home">
<button class="goto-prev" (click)="scroll('prev')" (mouseover)="paused=true"><span class="icon icon-back"></span></button>
<button class="goto-next" (click)="scroll('next')" (mouseover)="paused=true"><span class="icon icon-back"></span></button>
<div class="content" #scrollContent (touchstart)="swipe($event, 'start')" (touchend)="swipe($event, 'end')">
<div [ngClass]="'slide-' + item.width" *ngFor="let item of homeItems">
<div class="box" [ngClass]="'skew-' + (item.id % 6)" (click)="showDetails(item.id, item.title)" (mouseover)="paused=true" (mouseout)="paused=false">
<img class="image" *ngIf="item.loading" src="/assets/images/loader.svg" alt="loading">
<img class="image" [hidden]="item.loading" (load)="onLoad(item.id)" [src]="basePath+item.image">
<div class="text">
<span class="title">{{item.title}}</span>
<span class="type" *ngIf="section != 'exhibitions'">{{item.type}}</span>
<div class="date-container" *ngIf="section == 'exhibitions'">
<div class="date-row">
<span class="date-indication" *ngIf="item.date_from != item.date_to">from</span>
<span class="date-indication" *ngIf="item.date_from == item.date_to">on</span>
<span class="date">{{item.date_from | date}}</span>
</div>
<div class="date-row">
<span class="date" *ngIf="item.date_from != item.date_to"> <span class="date-indication">to</span> {{item.date_to | date}}</span>
</div>
</div>
<span class="tags">{{item.tags}}</span>
</div>
</div>
</div>
</div>
</div>
<app-spinner [show]="!loaded"></app-spinner>

185
src/app/home/home.component.scss

@ -0,0 +1,185 @@
@import "../../assets/scss/variables";
.component-home {
display: flex;
padding-top: 100px;
height: 100vh;
transform: skew(-15deg) rotate(-15deg);
z-index: 1;
.goto-prev,
.goto-next {
position: absolute;
height: 40px;
width: 60px;
border: none;
background: none;
color: $black;
font-size: $font-40;
appearance: none;
margin: 0;
padding: 5px;
cursor: pointer;
z-index: 2;
.icon {
&:before {
transform: translate(-50%, -50%);
position: absolute;
top: 50%;
left: 50%;
}
}
}
.goto-prev {
top: 60px;
left: 20px;
}
.goto-next {
top: calc(80vh + 10px);
right: 20px;
.icon {
&:before {
transform: translate(-50%, -50%) rotate(180deg);
}
}
}
.content {
display: inline-flex;
margin: 0;
margin-left: -50px;
//animation: slide 150s linear infinite;
transition: margin-left .5s;
@each $width in 1,2,3,4,5,6 {
.slide-#{$width} {
width: #{($width+2)*100}px;
}
}
.box {
position: relative;
display: flex;
background: $black-alpha2;
border-radius: 5px;
overflow: hidden;
margin: auto 0;
padding: 40px 20px;
height: calc(80vh - 90px);
min-height: 250px;
//max-height: 700px;
cursor: pointer;
transform: skew(-6deg, -6deg) rotate(6deg);
transition: transform .4s, background .4s, opacity .4s;
-webkit-backface-visibility: hidden;
.image {
position: absolute;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
object-fit: cover;
transform: translate(-50%, -50%);
opacity: .8;
filter: grayscale(100%) contrast(3);
transition: opacity .4s, filter .4s;
-webkit-backface-visibility: hidden;
z-index: 0;
}
.text {
display: block;
margin: auto;
text-align: center;
transform: translate(0%, 0%);
color: $yellow;
-webkit-backface-visibility: hidden;
z-index: 1;
.title {
display: block;
font-size: $font-20;
text-transform: uppercase;
font-weight: bold;
}
.type {
display: block;
font-size: $font-16;
font-weight: bold;
}
.tags {
display: block;
font-size: $font-12;
text-transform: uppercase;
font-weight: bold;
padding-top: 10px;
}
.date-container {
display: inline-flex;
flex-wrap: wrap;
.date-row {
display: block;
width: 100%;
.date {
display: inline-flex;
margin: auto;
font-size: $font-20;
}
.date-indication {
margin: 2px 5px auto 5px;
font-size: $font-12;
}
&:nth-of-type(2) {
margin-top: -12px;
}
}
}
}
&:hover {
background: $black;
transform: scale(1.4) rotate(14deg) skew(14deg, 0deg);
z-index: 50;
.image {
filter: grayscale(0%);
opacity: .5;
}
}
}
&:hover,
&.paused {
//animation: none;
animation-play-state: paused;
}
}
}
@media (min-width: map-get($grid-breakpoints, 'md')) {
.component-home {
.goto-prev {
left: 50px;
}
.goto-next {
right: 50px;
}
}
}
@keyframes slide {
0% {margin-left: 0;}
50% {margin-left: -100%;}
100% {margin-left: 0;}
}

25
src/app/home/home.component.spec.ts

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

129
src/app/home/home.component.ts

@ -0,0 +1,129 @@
import { Component, OnInit, ViewChild, ElementRef, Renderer2 } from '@angular/core'
import { Router, NavigationEnd } from '@angular/router'
import { ApisService } from '../services/apis.service'
import { environment } from '../../environments/environment'
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
@ViewChild('scrollContent') scrollContent: ElementRef
public basePath = `${environment.BASE_PATH}`
public loaded = false
public init = false
public homeItems: any = []
public section: string = 'portfolio'
public paused: boolean = false
private scrollPos: number = 0
private swipeCoord?: [number, number]
private swipeTime?: number
constructor(
private apisService: ApisService,
private router: Router,
private renderer: Renderer2
) { }
ngOnInit(): void {
this.apisService.getPortfolio(this.section, true).toPromise().then((response) => {
this.homeItems = response.items
let cnt = 0
let width = 0
let tot = 0
this.homeItems.forEach((e) => {
e.loading = true
e.width = Math.floor(Math.random()*4)+1
cnt++
})
},(error) => {
console.error('getPortfolio ERROR', error)
}).catch((e) => {
console.error('getPortfolio CATCH', e)
})
}
ngAfterContentInit() {
this.init = true
setInterval( () => {
if(!this.paused) {
const scrollWidth = 300
const scrollPos = parseInt(this.scrollContent.nativeElement.style.marginLeft) || 0
this.scrollPos = scrollPos - scrollWidth <= -(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) ?
-(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) : scrollPos - scrollWidth
this.scrollContent.nativeElement.style.marginLeft = this.scrollPos + 'px'
}
},2000)
}
showDetails(id, title = ''): void {
const section = this.section == 'exhibitions' ? 'exhibitions' : 'works'
this.router.navigate([`/detail/${section}/${id}/${title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '_')}`])
}
onLoad(id): void {
this.homeItems.filter(item => item.id == id)[0].loading = false
this.loaded = this.init && this.homeItems.every( (val) => !val.loading)
}
scroll(dir): void {
const scrollWidth = document.body.clientWidth / 3
const scrollPos = parseInt(this.scrollContent.nativeElement.style.marginLeft) || 0
this.paused = true
switch(dir) {
case 'prev':
this.scrollPos = scrollPos + scrollWidth >= 0 ? 0 : scrollPos + scrollWidth
break;
case 'next':
this.scrollPos = scrollPos - scrollWidth <= -(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) ?
-(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) : scrollPos - scrollWidth
break;
}
this.scrollContent.nativeElement.style.marginLeft = this.scrollPos + 'px'
}
swipe(e: TouchEvent, when: string): void {
const coord: [number, number] = [e.changedTouches[0].clientX, e.changedTouches[0].clientY]
const time = new Date().getTime()
const scrollWidth = document.body.clientWidth
const scrollPos = parseInt(this.scrollContent.nativeElement.style.marginLeft) || 0
this.paused = true
if (when === 'start') {
this.swipeCoord = coord
this.swipeTime = time
} else if (when === 'end') {
const direction = [coord[0] - this.swipeCoord[0], coord[1] - this.swipeCoord[1]]
const duration = time - this.swipeTime
if (duration < 1000
&& Math.abs(direction[0]) > 30
&& Math.abs(direction[0]) > Math.abs(direction[1] * 3)) {
const swipe = direction[0] < 0 ? 'next' : 'prev'
switch(swipe) {
case 'prev':
this.scrollPos = scrollPos + scrollWidth >= 0 ? 0 : scrollPos + scrollWidth
break;
case 'next':
this.scrollPos = scrollPos - scrollWidth <= -(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) ?
-(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) : scrollPos - scrollWidth
break;
}
this.scrollContent.nativeElement.style.marginLeft = this.scrollPos + 'px'
}
}
}
}

29
src/app/portfolio/portfolio.component.html

@ -0,0 +1,29 @@
<div class="component-portfolio">
<div class="row no-gutters row-container">
<div class="col-12 col-sm-6 mx-auto" [ngClass]="'col-md-' + item.width" *ngFor="let item of portfolioItems">
<div class="box" (click)="showDetails(item.id, item.title)">
<img class="loader" *ngIf="item.loading" src="/assets/images/loader.svg" alt="loading">
<img class="image" [hidden]="item.loading" (load)="onLoad(item.id)" [src]="basePath+item.image">
<div class="text">
<span class="title">{{item.title}}</span>
<span class="type" *ngIf="section != 'exhibitions'">{{item.type}}</span>
<div class="date-container" *ngIf="section == 'exhibitions'">
<div class="date-row">
<span class="date-indication" *ngIf="item.date_from != item.date_to">from</span>
<span class="date-indication" *ngIf="item.date_from == item.date_to">on</span>
<span class="date">{{item.date_from | date}}</span>
</div>
<div class="date-row">
<span class="date" *ngIf="item.date_from != item.date_to"> <span class="date-indication">to</span> {{item.date_to | date}}</span>
</div>
</div>
<span class="tags">{{item.tags}}</span>
</div>
</div>
</div>
</div>
</div>
<app-spinner [show]="!loaded"></app-spinner>

134
src/app/portfolio/portfolio.component.scss

@ -0,0 +1,134 @@
@import "../../assets/scss/variables";
.component-portfolio {
padding: 160px 0 80px 0;
.row-container {
position: relative;
transform: skew(-2deg, -2deg) rotate(-2deg);
width: calc(100% - 50px);
max-width: 1400px;
margin: auto;
z-index: 1;
.box {
position: relative;
display: flex;
border-radius: 4px;
overflow: hidden;
background: $black-alpha;
margin: 6px;
padding: 40px 20px;
min-height: 250px;
cursor: pointer;
transform: skew(6deg, -6deg) rotate(6deg);
transition: transform .4s, background .4s, box-shadow .4s;
-webkit-backface-visibility: hidden;
.image {
position: absolute;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
object-fit: cover;
transform: translate(-50%, -50%);
opacity: .9;
filter: grayscale(100%) brightness(.4);
transition: opacity .4s, filter .4s;
-webkit-backface-visibility: hidden;
z-index: 0;
}
.loader {
position: absolute;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
object-fit: cover;
transform: translate(-50%, -50%);
z-index: 0;
}
.text {
display: block;
margin: auto;
text-align: center;
transform: translate(0%, 0%);
color: $yellow;
//transition: color .4s;
-webkit-backface-visibility: hidden;
z-index: 1;
.title {
display: block;
font-size: $font-20;
text-transform: uppercase;
font-weight: bold;
}
.type {
display: block;
font-size: $font-16;
font-weight: bold;
}
.tags {
display: block;
font-size: $font-12;
text-transform: uppercase;
font-weight: bold;
padding-top: 10px;
}
.date-container {
display: inline-flex;
flex-wrap: wrap;
.date-row {
display: block;
width: 100%;
.date {
display: inline-flex;
margin: auto;
font-size: $font-20;
}
.date-indication {
margin: 2px 5px auto 5px;
font-size: $font-12;
}
&:nth-of-type(2) {
margin-top: -12px;
}
}
}
}
&:hover {
box-shadow: 0 0 20px $white-alpha2;
background: $black;
z-index: 50;
transform: scale(1.4) rotate(4deg) skew(3deg);
.image {
filter: grayscale(0%) brightness(1);
opacity: .7;
}
}
}
}
}
@media (min-width: map-get($grid-breakpoints, 'md')) {
.component-portfolio {
.row-container {
width: calc(100% - 100px);
}
}
}

25
src/app/portfolio/portfolio.component.spec.ts

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

84
src/app/portfolio/portfolio.component.ts

@ -0,0 +1,84 @@
import { Component, OnInit } from '@angular/core'
import { Router, NavigationEnd } from '@angular/router'
import { ApisService } from '../services/apis.service'
import { environment } from '../../environments/environment'
@Component({
selector: 'app-portfolio',
templateUrl: './portfolio.component.html',
styleUrls: ['./portfolio.component.scss']
})
export class PortfolioComponent implements OnInit {
public basePath = `${environment.BASE_PATH}`
public loaded = false
public init = false
public portfolioItems: any = []
public section: string = ''
constructor(
private apisService: ApisService,
private router: Router)
{ }
ngOnInit(): void {
this.section = this.router.url.split('/')[1]
this.apisService.getPortfolio(this.section).toPromise().then((response) => {
this.portfolioItems = response.items
let cnt = 0
let width = 0
let tot = 0
this.portfolioItems.forEach((e) => {
e.loading = true
switch (cnt) {
case 0:
width = Math.floor(Math.random()*3)+3
tot = width
cnt++
break;
case 1:
width = Math.floor(Math.random()*3)+3
cnt++
if(tot + width > 9) {
width = 12 - tot
tot = 0
cnt = 0
} else {
tot = tot + width
}
break;
case 2:
width = 12 - tot
tot = 0
cnt = 0
break;
}
e.width = width
})
},(error) => {
console.error('getPortfolio ERROR', error)
}).catch((e) => {
console.error('getPortfolio CATCH', e)
})
}
ngAfterContentInit() {
this.init = true
}
showDetails(id, title = ''): void {
const section = this.section == 'exhibitions' ? 'exhibitions' : 'works'
this.router.navigate([`/detail/${section}/${id}/${title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '_')}`])
}
onLoad(id): void {
this.portfolioItems.filter(item => item.id == id)[0].loading = false
this.loaded = this.init && this.portfolioItems.every( (val) => !val.loading)
}
}

16
src/app/services/apis.service.spec.ts

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ApisService } from './apis.service';
describe('ApisService', () => {
let service: ApisService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ApisService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

77
src/app/services/apis.service.ts

@ -0,0 +1,77 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders, HttpParams, HttpRequest } from '@angular/common/http'
import { Observable, Subject, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'
import { BaseService } from './base-service'
import { environment } from '../../environments/environment'
@Injectable({
providedIn: 'root'
})
export class ApisService extends BaseService {
private restApi = `${environment.API_URL}`
constructor(private http: HttpClient) {
super()
}
getPortfolio(section, randon = false): Observable<any> {
let urlApi = `${this.restApi}?query=${section}&random=${randon}`
return this.http.get<any>(urlApi).pipe(
catchError(this.handleError)
)
}
getDetails(section, id): Observable<any> {
let urlApi = `${this.restApi}?query=detail&type=${section}&id=${id}`
return this.http.get<any>(urlApi).pipe(
catchError(this.handleError)
)
}
// ADMIN SERVICES
uploadImage(body): Observable<any> {
let urlApi = `${this.restApi}upload.php`
return this.http.post<any>(urlApi, body).pipe(
catchError(this.handleError)
)
}
removeImage(body): Observable<any> {
let urlApi = `${this.restApi}remove.php`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
saveWork(body): Observable<any> {
let urlApi = `${this.restApi}work.php?act=save`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
deleteWork(body): Observable<any> {
let urlApi = `${this.restApi}work.php?act=delete`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
saveExhibition(body): Observable<any> {
let urlApi = `${this.restApi}exhibition.php?act=save`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
deleteExhibition(body): Observable<any> {
let urlApi = `${this.restApi}exhibition.php?act=delete`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
}

16
src/app/services/auth.service.spec.ts

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

33
src/app/services/auth.service.ts

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders, HttpParams, HttpRequest } from '@angular/common/http'
import { Observable, Subject, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'
import { BaseService } from './base-service'
import { environment } from '../../environments/environment'
@Injectable({
providedIn: 'root'
})
export class AuthService extends BaseService {
private restApi = `${environment.API_URL}`
constructor(private http: HttpClient) {
super()
}
login(body): Observable<any> {
let urlApi = `${this.restApi}auth.php?act=login`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
authCheck(body): Observable<any> {
let urlApi = `${this.restApi}auth.php?act=check`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
}

16
src/app/services/base-service.ts

@ -0,0 +1,16 @@
import { HttpErrorResponse } from "@angular/common/http"
import { throwError } from "rxjs"
import { ParseXML } from "./parse-xml"
export class BaseService {
constructor() { }
protected handleError(error: HttpErrorResponse) {
if(error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message)
}
return throwError( ParseXML.getXMLResponseMessage(error.error) )
}
}

13
src/app/services/parse-xml.ts

@ -0,0 +1,13 @@
export class ParseXML {
constructor() {}
public sanitize(str: string): string {
let sanitizeString = encodeURIComponent(str).replace(/%0A/g, '')
return decodeURIComponent(sanitizeString)
}
public static getXMLResponseMessage(responseBody: string): string {
let parseXMLClass = new ParseXML()
return parseXMLClass.sanitize(responseBody).match(/<Message>(.*?)<\/Message>/g)[0].replace(/<[^>]+>/g, '')
}
}

2
src/app/spinner/spinner.component.html

@ -0,0 +1,2 @@
<div class="spinner" #spinner></div>

15
src/app/spinner/spinner.component.scss

@ -0,0 +1,15 @@
@import "../../assets/scss/variables";
.spinner {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background: $yellow-alpha;
background-image: url('/assets/images/loader.svg');
background-size: 200px 200px;
background-repeat: no-repeat;
background-position: center center;
z-index: 100;
}

56
src/app/spinner/spinner.component.ts

@ -0,0 +1,56 @@
import { Component, OnInit, Input, ViewChild, ElementRef, SimpleChanges } from '@angular/core'
@Component({
selector: 'app-spinner',
templateUrl: './spinner.component.html',
styleUrls: ['./spinner.component.scss']
})
export class SpinnerComponent implements OnInit {
@ViewChild('spinner') spinner: ElementRef
@Input() show: boolean = false
ngOnInit(): void {
}
ngOnChanges(changes: SimpleChanges) {
if(changes.show && changes.show.firstChange) {
this.loader(changes.show.currentValue ? 'show' : 'hide')
}
}
loader(action) {
let op = 0
let timer = null
switch(action) {
case 'show':
op = 1
timer = setInterval(() => {
if(op <= 0.1){
clearInterval(timer)
this.spinner.nativeElement.style.display = 'none'
}
this.spinner.nativeElement.style.opacity = op
this.spinner.nativeElement.style.filter = 'alpha(opacity=' + op * 100 + ")"
op -= op * 0.1
}, 20)
break
case 'hide':
op = 0.1
this.spinner.nativeElement.style.display = 'block'
timer = setInterval(() => {
if(op >= 1){
clearInterval(timer)
}
this.spinner.nativeElement.style.opacity = op
this.spinner.nativeElement.style.filter = 'alpha(opacity=' + op * 100 + ")"
op += op * 0.1
}, 20)
break
}
}
}

BIN
src/assets/fonts/icomoon.eot

Binary file not shown.

38
src/assets/fonts/icomoon.svg

@ -0,0 +1,38 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="back" d="M424.633 593.293l-386.090-308.871h946.914v100h-661.836l163.48 130.785z" />
<glyph unicode="&#xe901;" glyph-name="arrow-left" d="M542.165 158.166l-225.835 225.835h494.336c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-494.336l225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-298.667-298.667c-4.096-4.096-7.168-8.789-9.259-13.824-2.176-5.205-3.243-10.795-3.243-16.341 0-10.923 4.181-21.845 12.501-30.165l298.667-298.667c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331z" />
<glyph unicode="&#xe902;" glyph-name="chevron-down" d="M225.835 524.502l256-256c16.683-16.683 43.691-16.683 60.331 0l256 256c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835-225.835 225.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331z" />
<glyph unicode="&#xe903;" glyph-name="delete" d="M896 725.334c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-512c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-535.296l-261.333 298.667 261.333 298.667zM896 810.667h-554.667c-12.8 0-24.235-5.632-32.128-14.549l-298.667-341.333c-14.208-16.213-13.909-40.192 0-56.192l298.667-341.333c8.448-9.643 20.224-14.549 32.128-14.592h554.667c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v512c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM481.835 524.502l97.835-97.835-97.835-97.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l97.835 97.835 97.835-97.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-97.835 97.835 97.835 97.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-97.835-97.835-97.835 97.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331z" />
<glyph unicode="&#xe904;" glyph-name="alert-circle" d="M981.333 426.667c0 129.579-52.565 246.997-137.472 331.861s-202.283 137.472-331.861 137.472-246.997-52.565-331.861-137.472-137.472-202.283-137.472-331.861 52.565-246.997 137.472-331.861 202.283-137.472 331.861-137.472 246.997 52.565 331.861 137.472 137.472 202.283 137.472 331.861zM896 426.667c0-106.069-42.923-201.984-112.469-271.531s-165.461-112.469-271.531-112.469-201.984 42.923-271.531 112.469-112.469 165.461-112.469 271.531 42.923 201.984 112.469 271.531 165.461 112.469 271.531 112.469 201.984-42.923 271.531-112.469 112.469-165.461 112.469-271.531zM469.333 597.334v-170.667c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v170.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM512 213.334c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667-42.667-19.115-42.667-42.667 19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe905;" glyph-name="alert-triangle" d="M475.648 752.043c3.115 5.248 7.893 10.325 14.251 14.165 10.069 6.101 21.589 7.595 32.256 4.949s20.224-9.216 26.197-19.115l361.216-603.008c2.987-5.12 5.077-11.435 5.461-18.517-0.64-15.701-5.077-25.216-12.075-32.384-7.68-7.851-18.219-12.715-29.568-12.843l-722.645 0.043c-6.485 0.043-13.696 1.749-20.523 5.717-10.197 5.888-17.024 15.317-19.883 25.899s-1.621 22.144 3.925 31.787zM402.432 795.904l-361.387-603.307c-18.005-31.189-21.589-66.133-13.141-97.707s29.013-60.075 59.648-77.739c19.797-11.435 41.643-17.067 62.933-17.152h722.901c35.797 0.384 67.712 15.104 90.581 38.485s36.864 55.595 36.48 90.923c-0.256 22.869-6.528 44.544-17.323 62.891l-361.557 603.605c-18.432 30.421-47.36 50.389-79.104 58.155s-66.603 3.456-96.811-14.891c-18.304-11.093-33.067-26.24-43.179-43.264zM469.333 554.667v-170.667c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v170.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM512 170.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667-42.667-19.115-42.667-42.667 19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe906;" glyph-name="check" d="M823.168 712.832l-439.168-439.168-183.168 183.168c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331l213.333-213.333c16.683-16.683 43.691-16.683 60.331 0l469.333 469.333c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0z" />
<glyph unicode="&#xe907;" glyph-name="corner-right-down" d="M170.667 725.334h298.667c35.371 0 67.285-14.293 90.496-37.504s37.504-55.125 37.504-90.496v-409.003l-140.501 140.501c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331l213.333-213.333c3.925-3.925 8.619-7.083 13.824-9.259 10.453-4.309 22.229-4.309 32.683 0 5.035 2.091 9.728 5.163 13.824 9.259l213.333 213.333c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-140.501-140.501v409.003c0 58.88-23.936 112.299-62.464 150.869s-91.989 62.464-150.869 62.464h-298.667c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe908;" glyph-name="edit" d="M469.333 810.667h-298.667c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v298.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-298.667c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501h298.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM759.168 862.166l-405.333-405.333c-5.205-5.163-9.259-11.947-11.221-19.84l-42.667-170.667c-1.664-6.4-1.792-13.568 0-20.693 5.717-22.869 28.885-36.779 51.755-31.061l170.667 42.667c7.125 1.749 14.080 5.504 19.84 11.221l405.333 405.333c25.984 25.984 38.997 60.16 38.997 94.165s-13.013 68.181-38.997 94.165-60.203 39.040-94.208 39.040-68.181-13.013-94.165-38.997zM819.499 801.835c9.344 9.344 21.504 13.995 33.835 13.995s24.491-4.651 33.835-13.995 13.995-21.504 13.995-33.835-4.651-24.491-13.995-33.835l-396.971-396.971-90.197-22.571 22.571 90.197z" />
<glyph unicode="&#xe909;" glyph-name="minus-square" d="M213.333 853.334c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v597.333c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM213.333 768h597.333c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-597.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501zM341.333 384h341.333c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-341.333c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe90a;" glyph-name="plus-square" d="M213.333 853.334c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v597.333c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM213.333 768h597.333c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-597.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501zM341.333 384h128v-128c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v128h128c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-128v128c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-128h-128c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe90b;" glyph-name="share" d="M128 426.667v-341.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h512c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v341.333c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-341.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-512c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v341.333c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM469.333 750.336v-451.669c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v451.669l97.835-97.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-170.667 170.667c-3.925 3.925-8.619 7.083-13.824 9.259-10.453 4.309-22.229 4.309-32.683 0-5.035-2.091-9.728-5.163-13.824-9.259l-170.667-170.667c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0z" />
<glyph unicode="&#xe90c;" glyph-name="trash-2" d="M768 640v-554.667c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-426.667c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v554.667zM725.333 725.334v42.667c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504h-170.667c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-42.667h-170.667c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h42.667v-554.667c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h426.667c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v554.667h42.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM384 725.334v42.667c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501h170.667c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-42.667zM384 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM554.667 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667z" />
<glyph unicode="&#xe90d;" glyph-name="x-square" d="M213.333 853.334c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v597.333c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM213.333 768h597.333c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-597.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501zM609.835 584.832l-97.835-97.835-97.835 97.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331l97.835-97.835-97.835-97.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l97.835 97.835 97.835-97.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-97.835 97.835 97.835 97.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0z" />
<glyph unicode="&#xf003;" glyph-name="envelope-o" d="M950.857 91.428v438.857c-12-13.714-25.143-26.286-39.429-37.714-81.714-62.857-164-126.857-243.429-193.143-42.857-36-96-80-155.429-80h-1.143c-59.429 0-112.571 44-155.429 80-79.429 66.286-161.714 130.286-243.429 193.143-14.286 11.429-27.429 24-39.429 37.714v-438.857c0-9.714 8.571-18.286 18.286-18.286h841.143c9.714 0 18.286 8.571 18.286 18.286zM950.857 692c0 14.286 3.429 39.429-18.286 39.429h-841.143c-9.714 0-18.286-8.571-18.286-18.286 0-65.143 32.571-121.714 84-162.286 76.571-60 153.143-120.571 229.143-181.143 30.286-24.571 85.143-77.143 125.143-77.143h1.143c40 0 94.857 52.571 125.143 77.143 76 60.571 152.571 121.143 229.143 181.143 37.143 29.143 84 92.571 84 141.143zM1024 713.143v-621.714c0-50.286-41.143-91.429-91.429-91.429h-841.143c-50.286 0-91.429 41.143-91.429 91.429v621.714c0 50.286 41.143 91.429 91.429 91.429h841.143c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf081;" glyph-name="twitter-square" horiz-adv-x="878" d="M731.429 602.286c-21.714-9.714-44.571-16-69.143-19.429 25.143 14.857 44 38.857 53.143 66.857-23.429-13.714-49.143-24-76.571-29.143-21.714 23.429-53.143 37.714-87.429 37.714-66.286 0-120-53.714-120-120 0-9.143 0.571-18.857 2.857-27.429-100 5.143-188.571 52.571-248 125.714-10.286-17.714-16.571-38.857-16.571-60.571 0-41.714 19.429-78.286 52-100-20 0.571-38.857 6.286-57.143 14.857v-1.143c0-58.286 44-106.857 98.857-117.714-10.286-2.857-18.286-4.571-29.143-4.571-7.429 0-14.857 1.143-22.286 2.286 15.429-47.429 59.429-82.286 112-83.429-41.143-32-92.571-51.429-149.143-51.429-9.714 0-19.429 0.571-28.571 1.714 53.143-33.714 116-53.714 184-53.714 220.571 0 341.714 182.857 341.714 341.714 0 5.143 0 10.286-0.571 15.429 23.429 16.571 44 37.714 60 62.286zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf082;" glyph-name="facebook-square" horiz-adv-x="878" d="M713.143 877.714c90.857 0 164.571-73.714 164.571-164.571v-548.571c0-90.857-73.714-164.571-164.571-164.571h-107.429v340h113.714l17.143 132.571h-130.857v84.571c0 38.286 10.286 64 65.714 64l69.714 0.571v118.286c-12 1.714-53.714 5.143-101.714 5.143-101.143 0-170.857-61.714-170.857-174.857v-97.714h-114.286v-132.571h114.286v-340h-304c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571z" />
<glyph unicode="&#xf095;" glyph-name="phone" horiz-adv-x="805" d="M804.571 242.286c0-20.571-9.143-60.571-17.714-79.429-12-28-44-46.286-69.714-60.571-33.714-18.286-68-29.143-106.286-29.143-53.143 0-101.143 21.714-149.714 39.429-34.857 12.571-68.571 28-100 47.429-97.143 60-214.286 177.143-274.286 274.286-19.429 31.429-34.857 65.143-47.429 100-17.714 48.571-39.429 96.571-39.429 149.714 0 38.286 10.857 72.571 29.143 106.286 14.286 25.714 32.571 57.714 60.571 69.714 18.857 8.571 58.857 17.714 79.429 17.714 4 0 8 0 12-1.714 12-4 24.571-32 30.286-43.429 18.286-32.571 36-65.714 54.857-97.714 9.143-14.857 26.286-33.143 26.286-50.857 0-34.857-103.429-85.714-103.429-116.571 0-15.429 14.286-35.429 22.286-49.143 57.714-104 129.714-176 233.714-233.714 13.714-8 33.714-22.286 49.143-22.286 30.857 0 81.714 103.429 116.571 103.429 17.714 0 36-17.143 50.857-26.286 32-18.857 65.143-36.571 97.714-54.857 11.429-5.714 39.429-18.286 43.429-30.286 1.714-4 1.714-8 1.714-12z" />
<glyph unicode="&#xf098;" glyph-name="phone-square" horiz-adv-x="878" d="M731.429 269.143c0 2.857 0 6.286-1.143 9.143-3.429 10.286-86.857 52.571-102.857 61.714-10.857 6.286-24 18.857-37.143 18.857-25.143 0-62.286-74.857-84.571-74.857-11.429 0-25.714 10.286-36 16-75.429 42.286-127.429 94.286-169.714 169.714-5.714 10.286-16 24.571-16 36 0 22.286 74.857 59.429 74.857 84.571 0 13.143-12.571 26.286-18.857 37.143-9.143 16-51.429 99.429-61.714 102.857-2.857 1.143-6.286 1.143-9.143 1.143-14.857 0-44-6.857-57.714-12.571-37.714-17.143-65.143-89.143-65.143-128.571 0-38.286 15.429-73.143 28.571-108.571 45.714-125.143 181.714-261.143 306.857-306.857 35.429-13.143 70.286-28.571 108.571-28.571 39.429 0 111.429 27.429 128.571 65.143 5.714 13.714 12.571 42.857 12.571 57.714zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf099;" glyph-name="twitter" horiz-adv-x="951" d="M925.714 717.714c-25.143-36.571-56.571-69.143-92.571-95.429 0.571-8 0.571-16 0.571-24 0-244-185.714-525.143-525.143-525.143-104.571 0-201.714 30.286-283.429 82.857 14.857-1.714 29.143-2.286 44.571-2.286 86.286 0 165.714 29.143 229.143 78.857-81.143 1.714-149.143 54.857-172.571 128 11.429-1.714 22.857-2.857 34.857-2.857 16.571 0 33.143 2.286 48.571 6.286-84.571 17.143-148 91.429-148 181.143v2.286c24.571-13.714 53.143-22.286 83.429-23.429-49.714 33.143-82.286 89.714-82.286 153.714 0 34.286 9.143 65.714 25.143 93.143 90.857-112 227.429-185.143 380.571-193.143-2.857 13.714-4.571 28-4.571 42.286 0 101.714 82.286 184.571 184.571 184.571 53.143 0 101.143-22.286 134.857-58.286 41.714 8 81.714 23.429 117.143 44.571-13.714-42.857-42.857-78.857-81.143-101.714 37.143 4 73.143 14.286 106.286 28.571z" />
<glyph unicode="&#xf09a;" glyph-name="facebook, facebook-f" horiz-adv-x="602" d="M548 944v-150.857h-89.714c-70.286 0-83.429-33.714-83.429-82.286v-108h167.429l-22.286-169.143h-145.143v-433.714h-174.857v433.714h-145.714v169.143h145.714v124.571c0 144.571 88.571 223.429 217.714 223.429 61.714 0 114.857-4.571 130.286-6.857z" />
<glyph unicode="&#xf0e0;" glyph-name="envelope" d="M1024 545.143v-453.714c0-50.286-41.143-91.429-91.429-91.429h-841.143c-50.286 0-91.429 41.143-91.429 91.429v453.714c17.143-18.857 36.571-35.429 57.714-49.714 94.857-64.571 190.857-129.143 284-197.143 48-35.429 107.429-78.857 169.714-78.857h1.143c62.286 0 121.714 43.429 169.714 78.857 93.143 67.429 189.143 132.571 284.571 197.143 20.571 14.286 40 30.857 57.143 49.714zM1024 713.143c0-64-47.429-121.714-97.714-156.571-89.143-61.714-178.857-123.429-267.429-185.714-37.143-25.714-100-78.286-146.286-78.286h-1.143c-46.286 0-109.143 52.571-146.286 78.286-88.571 62.286-178.286 124-266.857 185.714-40.571 27.429-98.286 92-98.286 144 0 56 30.286 104 91.429 104h841.143c49.714 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf166;" glyph-name="youtube-square" horiz-adv-x="878" d="M525.143 206.286v89.714c0 18.857-5.714 28.571-16.571 28.571-6.286 0-12.571-2.857-18.857-9.143v-128c6.286-6.286 12.571-9.143 18.857-9.143 10.857 0 16.571 9.143 16.571 28zM630.286 276h37.714v19.429c0 19.429-6.286 29.143-18.857 29.143s-18.857-9.714-18.857-29.143v-19.429zM304 428v-40h-45.714v-241.714h-42.286v241.714h-44.571v40h132.571zM418.857 356v-209.714h-38.286v22.857c-14.857-17.143-29.143-25.714-43.429-25.714-12 0-20.571 5.143-24 16-2.286 6.286-3.429 16-3.429 30.857v165.714h37.714v-154.286c0-8.571 0-13.714 0.571-14.857 0.571-5.714 3.429-8.571 8.571-8.571 8 0 15.429 5.714 24 17.714v160h38.286zM562.857 292.571v-83.429c0-18.857-1.143-33.143-4-41.714-4.571-16-14.857-24-30.286-24-13.143 0-26.286 8-38.857 23.429v-20.571h-38.286v281.714h38.286v-92c12 14.857 25.143 22.857 38.857 22.857 15.429 0 25.714-8 30.286-24 2.857-8.571 4-22.286 4-42.286zM706.286 218.857v-5.143c0-12.571-0.571-20.571-1.143-24.571-1.143-8.571-4-16-8.571-22.857-10.286-15.429-26.286-22.857-45.714-22.857-20 0-35.429 7.429-46.286 21.714-8 10.286-12 26.857-12 49.143v73.714c0 22.286 3.429 38.286 11.429 49.143 10.857 14.286 26.286 21.714 45.714 21.714 18.857 0 34.286-7.429 44.571-21.714 8-10.857 12-26.857 12-49.143v-43.429h-76v-37.143c0-19.429 6.286-29.143 19.429-29.143 9.143 0 14.857 5.143 17.143 14.857 0 2.286 0.571 10.857 0.571 25.714h38.857zM448.571 689.714v-89.143c0-19.429-6.286-29.143-18.286-29.143-12.571 0-18.286 9.714-18.286 29.143v89.143c0 19.429 5.714 29.714 18.286 29.714 12 0 18.286-10.286 18.286-29.714zM753.143 282.286v0c0 49.143 0 101.143-10.857 148.571-8 33.714-35.429 58.286-68 61.714-77.714 8.571-156.571 8.571-235.429 8.571-78.286 0-157.143 0-234.857-8.571-33.143-3.429-60.571-28-68-61.714-10.857-47.429-11.429-99.429-11.429-148.571v0c0-48.571 0-100.571 11.429-148.571 7.429-33.143 34.857-57.714 67.429-61.714 78.286-8.571 157.143-8.571 235.429-8.571s157.143 0 235.429 8.571c32.571 4 60 28.571 67.429 61.714 11.429 48 11.429 100 11.429 148.571zM321.714 654.286l51.429 169.143h-42.857l-29.143-111.429-30.286 111.429h-44.571c8.571-26.286 18.286-52.571 26.857-78.857 13.714-40 22.286-69.714 26.286-90.286v-114.857h42.286v114.857zM486.857 608v74.286c0 22.286-4 38.857-12 49.714-10.857 14.286-25.714 21.714-44.571 21.714-19.429 0-34.286-7.429-44.571-21.714-8-10.857-12-27.429-12-49.714v-74.286c0-22.286 4-38.857 12-49.714 10.286-14.286 25.143-21.714 44.571-21.714 18.857 0 33.714 7.429 44.571 21.714 8 10.286 12 27.429 12 49.714zM590.286 539.428h38.286v211.429h-38.286v-161.714c-8.571-12-16.571-17.714-24-17.714-5.143 0-8.571 2.857-9.143 9.143-0.571 1.143-0.571 5.714-0.571 14.857v155.429h-38.286v-167.429c0-14.857 1.143-24.571 3.429-31.429 4-10.286 12.571-15.429 24.571-15.429 14.286 0 28.571 8.571 44 25.714v-22.857zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf167;" glyph-name="youtube" horiz-adv-x="878" d="M554.857 240v-120.571c0-25.714-7.429-38.286-22.286-38.286-8.571 0-17.143 4-25.714 12.571v172c8.571 8.571 17.143 12.571 25.714 12.571 14.857 0 22.286-13.143 22.286-38.286zM748 239.428v-26.286h-51.429v26.286c0 25.714 8.571 38.857 25.714 38.857s25.714-13.143 25.714-38.857zM196 364h61.143v53.714h-178.286v-53.714h60v-325.143h57.143v325.143zM360.571 38.857h50.857v282.286h-50.857v-216c-11.429-16-22.286-24-32.571-24-6.857 0-10.857 4-12 12-0.571 1.714-0.571 8-0.571 20v208h-50.857v-223.429c0-20 1.714-33.143 4.571-41.714 4.571-14.286 16.571-21.143 33.143-21.143 18.286 0 37.714 11.429 58.286 34.857v-30.857zM605.714 123.428v112.571c0 26.286-1.143 45.143-5.143 56.571-6.286 21.143-20.571 32-40.571 32-18.857 0-36.571-10.286-53.143-30.857v124h-50.857v-378.857h50.857v27.429c17.143-21.143 34.857-31.429 53.143-31.429 20 0 34.286 10.857 40.571 31.429 4 12 5.143 30.857 5.143 57.143zM798.857 129.143v7.429h-52c0-20.571-0.571-32-1.143-34.857-2.857-13.714-10.286-20.571-22.857-20.571-17.714 0-26.286 13.143-26.286 39.429v49.714h102.286v58.857c0 30.286-5.143 52-15.429 66.286-14.857 19.429-34.857 29.143-60.571 29.143-26.286 0-46.286-9.714-61.143-29.143-10.857-14.286-16-36-16-66.286v-98.857c0-30.286 5.714-52.571 16.571-66.286 14.857-19.429 34.857-29.143 61.714-29.143s48 10.286 61.714 30.286c6.286 9.143 10.857 19.429 12 30.857 1.143 5.143 1.143 16.571 1.143 33.143zM451.429 650.857v120c0 26.286-7.429 39.429-24.571 39.429-16.571 0-24.571-13.143-24.571-39.429v-120c0-26.286 8-40 24.571-40 17.143 0 24.571 13.714 24.571 40zM862.286 221.714c0-65.714-0.571-136-14.857-200-10.857-45.143-47.429-78.286-91.429-82.857-105.143-12-211.429-12-317.143-12s-212 0-317.143 12c-44 4.571-81.143 37.714-91.429 82.857-14.857 64-14.857 134.286-14.857 200v0c0 66.286 0.571 136 14.857 200 10.857 45.143 47.429 78.286 92 83.429 104.571 11.429 210.857 11.429 316.571 11.429s212 0 317.143-11.429c44-5.143 81.143-38.286 91.429-83.429 14.857-64 14.857-133.714 14.857-200zM292 950.857h58.286l-69.143-228v-154.857h-57.143v154.857c-5.143 28-16.571 68-34.857 121.143-12.571 35.429-25.143 71.429-37.143 106.857h60.571l40.571-150.286zM503.429 760.571v-100c0-30.286-5.143-53.143-16-67.429-14.286-19.429-34.286-29.143-60.571-29.143-25.714 0-45.714 9.714-60 29.143-10.857 14.857-16 37.143-16 67.429v100c0 30.286 5.143 52.571 16 66.857 14.286 19.429 34.286 29.143 60 29.143 26.286 0 46.286-9.714 60.571-29.143 10.857-14.286 16-36.571 16-66.857zM694.857 853.143v-285.143h-52v31.429c-20.571-24-40-35.429-58.857-35.429-16.571 0-28.571 6.857-33.714 21.143-2.857 8.571-4.571 22.286-4.571 42.857v225.143h52v-209.714c0-12 0-18.857 0.571-20 1.143-8 5.143-12.571 12-12.571 10.286 0 21.143 8 32.571 24.571v217.714h52z" />
<glyph unicode="&#xf194;" glyph-name="vimeo-square" horiz-adv-x="878" d="M738.286 586.286c4 82.857-26.857 124.571-92 126.857-88 2.857-147.429-46.857-178.286-149.143 16 6.857 31.429 10.857 46.857 10.857 32 0 46.286-18.286 42.286-54.857-1.714-21.714-16-53.714-42.286-95.429-26.857-42.286-46.857-62.857-60-62.857-17.143 0-32 32-46.857 96.571-4.571 19.429-13.143 67.429-25.714 145.714-11.429 72-41.714 105.714-91.429 101.143-20.571-2.286-52.571-20.571-93.714-57.143-30.857-26.857-61.143-54.857-92.571-82.286l29.714-38.286c28.571 19.429 45.143 29.714 49.714 29.714 21.714 0 42.286-34.286 61.143-102.286 17.143-62.857 34.286-125.143 51.429-188 25.714-68 56.571-102.286 93.714-102.286 59.429 0 132.571 56 218.857 168 83.429 107.429 126.857 192 129.143 253.714zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf1d2;" glyph-name="git-square" horiz-adv-x="878" d="M332.571 203.428c0-30.857-28-37.714-53.143-37.714-24.571 0-61.143 4-61.143 36 0 31.429 30.857 36.571 56 36.571 24 0 58.286-4 58.286-34.857zM312 469.714c0-28.571-11.429-48.571-42.286-48.571-31.429 0-44 18.286-44 48s11.429 51.429 44 51.429c29.143 0 42.286-24 42.286-50.857zM406.857 512.571v71.429c-24.571-9.143-50.857-16.571-77.143-16.571-18.857 10.857-40.571 16.571-62.857 16.571-65.143 0-116.571-48-116.571-114.286 0-35.429 23.429-84.571 58.857-96.571v-1.714c-18.286-8-21.714-30.286-21.714-48.571 0-18.857 6.857-34.286 23.429-44v-1.714c-38.857-12.571-64.571-37.143-64.571-79.429 0-72.571 69.143-93.143 129.714-93.143 73.143 0 128 26.857 128 107.429 0 57.143-52 74.286-99.429 82.857-16 2.857-43.429 14.286-43.429 34.286 0 18.857 10.286 26.857 28 29.714 58.286 11.429 95.429 56.571 95.429 116.571 0 10.286-2.286 20-5.714 29.714 9.143 2.286 18.857 4.571 28 7.429zM440.571 273.143h78.286c-1.143 15.429-1.143 31.429-1.143 46.857v221.143c0 13.143 0 26.286 1.143 39.429h-78.286c1.714-13.143 1.714-27.429 1.714-40.571v-224c0-14.286 0-28.571-1.714-42.857zM731.429 282.286v69.143c-11.429-8-25.143-12-38.857-12-25.714 0-30.286 25.714-30.286 46.857v128.571h29.714c10.286 0 20-1.143 30.286-1.143v66.857h-60c0 19.429-1.143 38.857 1.714 58.286h-80c1.714-10.286 2.286-20.571 2.286-31.429v-26.857h-34.286v-66.857c6.857 0.571 13.714 1.714 21.143 1.714 4 0 8.571-0.571 13.143-0.571v-1.143h-1.143v-124c0-61.714 9.143-121.143 84.571-121.143 21.143 0 42.857 3.429 61.714 13.714zM528 685.714c0 26.857-20 52-48 52s-48.571-24.571-48.571-52c0-26.857 21.143-50.857 48.571-50.857s48 24.571 48 50.857zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf1d3;" glyph-name="git" d="M340 85.714c0 50.286-55.429 57.143-94.286 57.143-40.571 0-90.286-8.571-90.286-59.429 0-51.429 58.857-57.714 98.286-57.714 41.714 0 86.286 10.286 86.286 60zM306.286 517.143c0 42.857-20.571 81.714-68 81.714-52.571 0-70.857-34.857-70.857-82.857 0-47.429 20.571-77.143 70.857-77.143 49.714 0 68 32 68 78.286zM460 702.286v-115.429c-14.857-5.143-29.714-9.143-45.143-12.571 5.714-15.429 9.143-31.429 9.143-48 0-96.571-59.429-170.286-154.286-188-28.571-5.714-45.143-17.714-45.143-48.571 0-87.429 230.857-28 230.857-189.143 0-130.857-88.571-173.714-207.429-173.714-97.714 0-209.143 32.571-209.143 150.286 0 68.571 41.714 108 104 128.571v2.286c-26.286 16-38.286 41.143-38.286 72 0 29.143 6.286 65.143 36 78.286v2.286c-57.714 19.429-95.429 98.857-95.429 156.571 0 106.857 82.857 185.143 188.571 185.143 35.429 0 70.857-9.143 101.714-26.857 42.857 0 85.143 11.429 124.571 26.857zM641.714 198.857h-126.857c2.286 25.714 2.286 50.857 2.286 76.571v348c0 24.571 0.571 49.143-2.286 73.143h126.857c-2.857-23.429-2.286-47.429-2.286-70.857v-350.286c0-25.714 0-50.857 2.286-76.571zM985.143 325.714v-112c-30.286-16.571-65.143-22.286-99.429-22.286-122.286 0-136.571 96.571-136.571 196v200.571h1.143v2.286c-7.429 0-14.286 1.143-21.143 1.143-11.429 0-22.857-1.714-33.714-3.429v108.571h54.857v43.429c0 17.143-0.571 34.286-3.429 50.857h129.714c-4.571-31.429-3.429-62.857-3.429-94.286h97.714v-108.571c-16.571 0-33.143 2.286-49.143 2.286h-48.571v-208.571c0-33.714 7.429-74.857 49.714-74.857 22.286 0 44 6.286 62.286 18.857zM656 866.857c0-42.857-33.143-82.857-77.143-82.857-45.143 0-78.857 39.429-78.857 82.857 0 44 33.143 84 78.857 84 45.143 0 77.143-41.143 77.143-84z" />
<glyph unicode="&#xf27d;" glyph-name="vimeo" horiz-adv-x="1029" d="M976.571 654.857c-4-90.286-67.429-214.286-189.714-372-126.857-164-233.143-246.286-321.143-246.286-54.286 0-100 50.286-137.143 150.286-25.143 91.429-50.286 183.429-75.429 275.429-27.429 100-57.714 149.714-89.714 149.714-6.857 0-30.857-14.286-72.571-43.429l-44 56c45.714 40.571 90.857 81.714 136 121.143 60.571 53.714 106.857 80.571 137.714 83.429 72.571 6.857 116.571-42.286 133.714-148 17.714-114.286 30.857-185.714 37.714-213.143 21.143-94.857 43.429-142.286 68.571-142.286 19.429 0 48.571 30.286 88 92 38.857 61.714 59.429 108.571 62.286 140.571 5.143 53.143-15.429 79.429-62.286 79.429-22.286 0-45.143-5.143-69.143-14.857 45.714 149.714 133.143 222.286 262.286 218.286 95.429-2.857 140.571-65.143 134.857-186.286z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 28 KiB

BIN
src/assets/fonts/icomoon.ttf

Binary file not shown.

BIN
src/assets/fonts/icomoon.woff

Binary file not shown.

1
src/assets/fonts/selection.json

File diff suppressed because one or more lines are too long

7
src/assets/images/angle-down.svg

@ -0,0 +1,7 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="768" height="768" viewBox="0 0 768 768">
<title></title>
<g id="icomoon-ignore">
</g>
<path d="M169.376 310.624l192 192c12.512 12.512 32.768 12.512 45.248 0l192-192c12.512-12.512 12.512-32.768 0-45.248s-32.768-12.512-45.248 0l-169.376 169.376-169.376-169.376c-12.512-12.512-32.768-12.512-45.248 0s-12.512 32.768 0 45.248z"></path>
</svg>

After

Width:  |  Height:  |  Size: 432 B

BIN
src/assets/images/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

11
src/assets/images/loader.svg

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%; display: block; shape-rendering: auto;" width="414px" height="414px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="0" fill="none" stroke="#a2dc02" stroke-width="2">
<animate attributeName="r" repeatCount="indefinite" dur="1s" values="0;20" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline" begin="-0.5s"></animate>
<animate attributeName="opacity" repeatCount="indefinite" dur="1s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline" begin="-0.5s"></animate>
</circle>
<circle cx="50" cy="50" r="0" fill="none" stroke="#ffffff" stroke-width="2">
<animate attributeName="r" repeatCount="indefinite" dur="1s" values="0;20" keyTimes="0;1" keySplines="0 0.2 0.8 1" calcMode="spline"></animate>
<animate attributeName="opacity" repeatCount="indefinite" dur="1s" values="1;0" keyTimes="0;1" keySplines="0.2 0 0.8 1" calcMode="spline"></animate>
</circle>
<!-- [ldio] generated by https://loading.io/ --></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/assets/images/loader.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

109
src/assets/images/logo.svg

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg291"
version="1.1"
viewBox="0 0 134.02536 119.09393"
height="119.09393mm"
width="134.02536mm">
<defs
id="defs285" />
<metadata
id="metadata288">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(26.947181,-31.923306)"
id="layer1">
<g
transform="matrix(0.85764954,0,0,0.85764954,-2439.8694,1481.7208)"
id="g5142"
style="opacity:1;stroke-width:0.3085;stop-opacity:1">
<g
transform="matrix(1.6074173,-0.06506215,0.06506215,1.6074173,2783.9869,396.74804)"
id="g4123"
style="opacity:1;stroke-width:0.191764;stop-opacity:1">
<path
id="rect4119"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.17434;stroke-linecap:round;stop-opacity:1"
d="m 78.642441,-1290.2797 h 77.756669 c 4.67863,0 8.44519,3.7666 8.44519,8.4452 v 63.1761 c 0,4.6786 -3.76656,8.4452 -8.44519,8.4452 H 78.642441 c -4.678636,0 -8.445191,-3.7666 -8.445191,-8.4452 v -63.1761 c 0,-4.6786 3.766555,-8.4452 8.445191,-8.4452 z" />
<path
d="m 101.70376,-1285.4686 52.03314,-6.6491 c 7.49898,-0.9582 14.01226,7.6726 12.14454,14.9982 l -13.74945,53.9284 c -1.86771,7.3256 -7.4443,11.8412 -14.99818,12.1445 l -55.608725,2.2332 c -7.553888,0.3035 -14.641402,-7.8624 -12.144536,-14.9981 l 17.325021,-49.5126 c 2.496887,-7.1358 7.499187,-11.1862 14.99819,-12.1445 z"
style="opacity:1;fill:#282828;fill-opacity:1;stroke-width:2.17434;stroke-linecap:round;stop-opacity:1"
id="path4121" />
</g>
<g
id="g4147-8-9"
style="opacity:1;stroke-width:0.21846;stop-opacity:1"
transform="matrix(1.4786476,0,0,1.3486498,1787.459,186.3728)">
<path
id="rect4125-6-4"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 719.07696,-1327.05 h 9.51459 c 0.66504,0 1.20044,0.5464 1.20044,1.2251 v 9.5083 c 0,0.6787 -0.5354,1.2252 -1.20044,1.2252 h -9.51459 c -0.66504,0 -1.20043,-0.5465 -1.20043,-1.2252 v -9.5083 c 0,-0.6787 0.53539,-1.2251 1.20043,-1.2251 z" />
<path
id="rect4127-8-2"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 733.01366,-1333.8127 h 14.62838 c 0.66504,0 1.20043,0.5464 1.20043,1.2251 v 14.6406 c 0,0.6787 -0.53539,1.2252 -1.20043,1.2252 h -14.62838 c -0.66503,0 -1.20043,-0.5465 -1.20043,-1.2252 v -14.6406 c 0,-0.6787 0.5354,-1.2251 1.20043,-1.2251 z" />
<path
id="rect4129-9-1"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 752.04498,-1334.4426 h 11.89331 c 0.66504,0 1.20043,0.5464 1.20043,1.2251 v 11.8957 c 0,0.6787 -0.53539,1.2251 -1.20043,1.2251 h -11.89331 c -0.66504,0 -1.20044,-0.5464 -1.20044,-1.2251 v -11.8957 c 0,-0.6787 0.5354,-1.2251 1.20044,-1.2251 z" />
<path
id="rect4131-3-7"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 752.86669,-1316.9962 h 5.19802 c 0.66504,0 1.20043,0.5464 1.20043,1.2251 v 5.1761 c 0,0.6788 -0.53539,1.2252 -1.20043,1.2252 h -5.19802 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2252 v -5.1761 c 0,-0.6787 0.53539,-1.2251 1.20043,-1.2251 z" />
<path
id="rect4133-1-75"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 737.72674,-1314.4257 h 10.71582 c 0.66504,0 1.20043,0.5465 1.20043,1.2252 v 10.7139 c 0,0.6787 -0.53539,1.2251 -1.20043,1.2251 h -10.71582 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2251 v -10.7139 c 0,-0.6787 0.53539,-1.2252 1.20043,-1.2252 z" />
<path
id="rect4135-2-60"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 728.25585,-1313.5555 h 4.41987 c 0.66504,0 1.20043,0.5464 1.20043,1.2251 v 4.3951 c 0,0.6788 -0.53539,1.2252 -1.20043,1.2252 h -4.41987 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2252 v -4.3951 c 0,-0.6787 0.53539,-1.2251 1.20043,-1.2251 z" />
<path
id="rect4137-7-9"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 759.00152,-1349.4855 h 10.63346 c 0.66504,0 1.20043,0.5464 1.20043,1.2252 v 10.6312 c 0,0.6787 -0.53539,1.2251 -1.20043,1.2251 h -10.63346 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2251 v -10.6312 c 0,-0.6788 0.53539,-1.2252 1.20043,-1.2252 z" />
<path
id="rect4139-2-1"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 769.62182,-1362.4576 h 7.93585 c 0.66504,0 1.20043,0.5464 1.20043,1.2251 v 7.9238 c 0,0.6788 -0.53539,1.2252 -1.20043,1.2252 h -7.93585 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2252 v -7.9238 c 0,-0.6787 0.53539,-1.2251 1.20043,-1.2251 z" />
<path
id="rect4141-3-7"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 786.83764,-1379.2218 h 2.58218 c 0.66504,0 1.20043,0.5464 1.20043,1.2252 v 2.5507 c 0,0.6788 -0.53539,1.2252 -1.20043,1.2252 h -2.58218 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2252 v -2.5507 c 0,-0.6788 0.53539,-1.2252 1.20043,-1.2252 z" />
<path
id="rect4143-9-8"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 779.15331,-1372.4268 h 4.94383 c 0.66504,0 1.20043,0.5465 1.20043,1.2252 v 4.921 c 0,0.6787 -0.53539,1.2251 -1.20043,1.2251 h -4.94383 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2251 v -4.921 c 0,-0.6787 0.53539,-1.2252 1.20043,-1.2252 z" />
<path
id="rect4145-3-8"
style="opacity:1;fill:#a2dc02;fill-opacity:1;stroke-width:2.47704;stroke-linecap:round;stop-opacity:1"
transform="matrix(1,0,0.00451391,0.99998981,0,0)"
d="m 793.26153,-1383.2676 h 1.42925 c 0.66504,0 1.20044,0.5464 1.20044,1.2252 v 1.3937 c 0,0.6787 -0.5354,1.2251 -1.20044,1.2251 h -1.42925 c -0.66504,0 -1.20043,-0.5464 -1.20043,-1.2251 v -1.3937 c 0,-0.6788 0.53539,-1.2252 1.20043,-1.2252 z" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

10
src/assets/scss/fonts.scss

@ -0,0 +1,10 @@
@import url('https://fonts.googleapis.com/css2?family=Abel&display=swap');
.font-primary { font-family: $font-primary; }
.font-secondary { font-family: $font-secondary; }
.font-bold { font-weight: bold !important; }
.font-light { font-weight: ight !important; }
@each $size in 8, 10, 12, 13, 14, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 46, 48, 50, 52, 54, 60, 72 {
.font-#{$size} {font-size: #{$size/16}rem !important;}
}

90
src/assets/scss/forms.scss

@ -0,0 +1,90 @@
input,
select,
button,
textarea {
border: none;
border-radius: 4px;
background: $white;
appearance: none;
font-family: $font-primary;
font-size: $font-20;
resize: none;
&::-ms-clear {
display: none;
}
&:focus {outline:none;}
&::-moz-focus-inner {border:0;}
}
input[type=text],
input[type=password],
input[type=file],
select {
color: $black;
padding: 10px 20px;
width: 100%;
text-align: left;
box-sizing: border-box;
&:focus::placeholder {
color: transparent;
}
}
.input-text {
padding: 10px 20px !important;
}
.input-file {
padding: 6px 20px !important;
}
.input-textarea,
.angular-editor {
border-radius: 4px;
background: $white;
padding: 10px;
width: 100%;
}
.input-select {
padding: 9px 20px !important;
background-image: url('/assets/images/angle-down.svg');
background-size: 28px;
background-position: right 10px top 10px;
background-repeat: no-repeat;
}
.button {
position: relative;
appearance: none;
color: $white;
border: none;
border-radius: 4px;
background: $black;
display: inline-block;
padding: 10px 20px 10px 20px !important;
text-align: center;
font-family: $font-20;
text-transform: uppercase;
font-weight: 500;
transition: opacity .3s;
white-space: nowrap;
outline: none;
cursor: pointer;
&:disabled {
opacity: 0.5;
}
&.button-white {
background: $white;
color: $black !important;
}
&.button-transparent {
background: $white-alpha2;
color: $black !important;
}
}

39
src/assets/scss/global.scss

@ -1,6 +1,43 @@
body {
padding: 0;
margin: 0;
height: 100%;
font-family: $font-primary;
font-size: $font-20;
color: $black;
background: $yellow;
overflow-x: hidden;
&.no-scroll {
overflow: hidden;
}
}
a,
li,
button {
outline: none !important;
&:active,
&:focus {
outline: none !important;
}
}
@each $size in 10,20,30,40,50,60,70,80,90,100 {
.w-#{$size} {width: percentage($size/100) !important;}
}
.text-right {text-align: right !important;}
.text-center {text-align: center !important;}
.text-left {text-align: left !important;}
.particles {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background: radial-gradient(circle, transparent 0%, transparent 85%, rgba(255,255,255,0.2) 95%, rgba(255,255,255,0.3) 100%);
z-index: -1;
}

114
src/assets/scss/icons.scss

@ -0,0 +1,114 @@
@font-face {
font-family: 'icomoon';
src: url('../fonts/icomoon.eot?9ti7zb');
src: url('../fonts/icomoon.eot?9ti7zb#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?9ti7zb') format('truetype'),
url('../fonts/icomoon.woff?9ti7zb') format('woff'),
url('../fonts/icomoon.svg?9ti7zb#icomoon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-alert-circle:before {
content: "\e904";
}
.icon-alert-triangle:before {
content: "\e905";
}
.icon-arrow-left:before {
content: "\e901";
}
.icon-check:before {
content: "\e906";
}
.icon-chevron-down:before {
content: "\e902";
}
.icon-corner-right-down:before {
content: "\e907";
}
.icon-delete:before {
content: "\e903";
}
.icon-edit:before {
content: "\e908";
}
.icon-minus-square:before {
content: "\e909";
}
.icon-plus-square:before {
content: "\e90a";
}
.icon-share:before {
content: "\e90b";
}
.icon-trash-2:before {
content: "\e90c";
}
.icon-x-square:before {
content: "\e90d";
}
.icon-envelope-o:before {
content: "\f003";
}
.icon-twitter-square:before {
content: "\f081";
}
.icon-facebook-square:before {
content: "\f082";
}
.icon-phone:before {
content: "\f095";
}
.icon-phone-square:before {
content: "\f098";
}
.icon-twitter:before {
content: "\f099";
}
.icon-facebook:before {
content: "\f09a";
}
.icon-facebook-f:before {
content: "\f09a";
}
.icon-envelope:before {
content: "\f0e0";
}
.icon-youtube-square:before {
content: "\f166";
}
.icon-youtube:before {
content: "\f167";
}
.icon-vimeo-square:before {
content: "\f194";
}
.icon-git-square:before {
content: "\f1d2";
}
.icon-git:before {
content: "\f1d3";
}
.icon-vimeo:before {
content: "\f27d";
}
.icon-back:before {
content: "\e900";
}

6
src/assets/scss/main.scss

@ -1,2 +1,8 @@
@import "./variables";
@import "./fonts";
@import "./icons";
@import "./forms";
@import "./global";
@import "node_modules/bootstrap/scss/bootstrap-grid";

36
src/assets/scss/variables.scss

@ -3,15 +3,15 @@ $grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px
lg: 1024px,
xl: 1440px
);
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px
lg: 990px,
xl: 1418px
);
$grid-columns: 12;
@ -23,23 +23,23 @@ $breadcrumb-height: 60px;
// Colors
$white: #fff;
$black: #000;
$gray: #e8e8e8;
$gray2: #666;
$light-gray: #f5f5f5;
$light-gray2: #c3c3c3;
$dark-gray: #47464e;
$dark-gray2: #2c2c2c;
$gold: #f1c060;
$red: #ea2d31;
$green: #55aa2a;
$blue: #3785ff;
$gray: #999;
$light-gray: #eee;
$dark-gray: #333;
$red: #d00;
$green: #0c0;
$white-alpha: rgba(255, 255, 255, 0.9);
$white-alpha-light: rgba(255, 255, 255, 0.2);
$yellow: #a2dc02;
$white-alpha: rgba(255, 255, 255, 0.8);
$white-alpha2: rgba(255, 255, 255, 0.4);
$black-alpha: rgba(0, 0, 0, 0.8);
$black-alpha2: rgba(0, 0, 0, 0.4);
$yellow-alpha: rgba(160, 220, 0, 0.8);
// Fonts
$font-primary: 'Akzidenz';
$font-secondary: 'Akzidenz';
$font-primary: 'Abel';
$font-secondary: 'Abel';
$font-icon: 'icomoon';
// Font-size variables

52
src/config/config.ts

@ -0,0 +1,52 @@
import { environment } from '../environments/environment'
import { AngularEditorConfig } from '@kolkov/angular-editor'
export const editorConfig: AngularEditorConfig = {
editable: true,
spellcheck: true,
height: '300px',
translate: 'no',
enableToolbar: true,
showToolbar: true,
placeholder: 'Enter text here...',
defaultParagraphSeparator: 'p',
defaultFontName: '',
defaultFontSize: '',
uploadUrl: `${environment.API_URL}upload.php`,
uploadWithCredentials: false,
sanitize: true,
toolbarPosition: 'top',
toolbarHiddenButtons: [
[
'undo',
'redo',
//'bold',
//'italic',
//'underline',
'strikeThrough',
'subscript',
'superscript',
//'justifyLeft',
//'justifyCenter',
//'justifyRight',
//'justifyFull',
'indent',
'outdent',
'insertUnorderedList',
'insertOrderedList',
'heading',
'fontName',
'fontSize',
'textColor',
'backgroundColor',
'customClasses',
//'link',
'unlink',
//'insertImage',
'insertVideo',
'insertHorizontalRule',
//'removeFormat',
//'toggleEditorMode'
]
]
}

6
src/environments/environment.prod.ts

@ -1,3 +1,5 @@
export const environment = {
production: true
};
production: true,
API_URL: `https://www.dslak.it/apis/`,
BASE_PATH: `https://www.dslak.it`
}

19
src/environments/environment.ts

@ -1,16 +1,5 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
production: false,
API_URL: `http://dslakng.local/apis/`,
BASE_PATH: `http://dslakng.local`
}

BIN
src/favicon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

BIN
src/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

4
src/index.html

@ -4,8 +4,8 @@
<meta charset="utf-8">
<title>DslakWebsite</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<meta name="viewport" content="width=device-width, user-scalable=no,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
<link rel="icon" type="image/x-icon" href="assets/images/favicon.png">
</head>
<body>
<app-root></app-root>

2
src/main.ts

@ -4,6 +4,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import 'hammerjs';
if (environment.production) {
enableProdMode();
}

Loading…
Cancel
Save