diff --git a/CascadingStyleSheets/General/fontface.css b/CascadingStyleSheets/General/fontface.css index fd16dba..2540fa5 100644 --- a/CascadingStyleSheets/General/fontface.css +++ b/CascadingStyleSheets/General/fontface.css @@ -13,6 +13,16 @@ src: url(/File/Font/bailufeiyunshouxieti.ttf); } +@font-face { + font-family: "cangefeibaiw02"; + src: url(/File/Font/cangefeibaiw02.ttf); +} + +@font-face { + font-family: "chengmingshouxieti"; + src: url(/File/Font/chengmingshouxieti.ttf); +} + @font-face { font-family: "font-MZD"; src: url(/File/Font/font-MZD.ttf); diff --git a/CascadingStyleSheets/General/global.css b/CascadingStyleSheets/General/global.css index 926e7f8..5a78689 100644 --- a/CascadingStyleSheets/General/global.css +++ b/CascadingStyleSheets/General/global.css @@ -3,32 +3,53 @@ /* Background */ --bg-color: #d8dad7; + /* Scroll Bar */ + --scroll-bar-width: 0.5vw; + /* Font Size */ --font-size-normal: 1.2em; --font-size-2em: 2em; + --font-size-2_5em: 2.5em; + --font-size-3em: 3em; --font-size-4em: 4em; /* Font Color */ --font-color-title: #4a4a4a; --font-color-normal: #888888; --font-color-normal-selected: #111111; + --font-color-light-normal: #555555; /* Font face */ --fontface-title: "PPAcma-Black", "YeZiGongChangTangYingHei-2"; --fontface-title-2: "GenJyuuGothic-Normal"; --fontface-content: "unifontdianzhenhei"; + --fontface-content-2: "bailufeiyunshouxieti"; + --fontface-content-3: "cangefeibaiw02"; --fontface-art: "amelia", "font-MZD"; + + /* Display */ + --display-phone: none; + --display-pc: initial; } @media screen and (max-width: 750px) { :root { + /* Scroll Bar */ + --scroll-bar-width: 2vw; + /* Font */ --font-size-normal: 1em; + --font-size-2em: 1.5em; + --font-size-2_5em: 1.75em; + --font-size-3em: 2.5em; --font-size-4em: 3em; - --font-size-2em: 1.75em; + + /* Display */ + --display-phone: initial; + --display-pc: none; } } \ No newline at end of file diff --git a/CascadingStyleSheets/General/loading.css b/CascadingStyleSheets/General/loading.css index 27212ef..68d49c1 100644 --- a/CascadingStyleSheets/General/loading.css +++ b/CascadingStyleSheets/General/loading.css @@ -3,6 +3,8 @@ --loading-content-height: 60vh; --loading-content-width: 60vw; + + --loading-progress-text-align: center; } @media screen and (max-width: 750px) { @@ -11,6 +13,8 @@ --loading-content-height: 80vh; --loading-content-width: 80vw; + + --loading-progress-text-align: left; } } @@ -19,11 +23,21 @@ background: var(--bg-color); font-family: var(--fontface-content); + position: fixed; + top: 0; + left: 0; height: 100vh; width: 100vw; overflow: hidden; + opacity: 1; z-index: 1000; + transition: all 0.5s; +} + +#box_loading.hide { + opacity: 0; + z-index: -1000; } #box_loading div > p:first-child { @@ -69,3 +83,10 @@ opacity: 0; transform: translateX(1em); } + +#box_loading div > ol:nth-child(2) p { + + color: var(--font-color-normal); + font-size: var(--font-size-4em); + text-align: var(--loading-progress-text-align); +} diff --git a/CascadingStyleSheets/General/navigation.css b/CascadingStyleSheets/General/navigation.css index f1e05b4..baa58e0 100644 --- a/CascadingStyleSheets/General/navigation.css +++ b/CascadingStyleSheets/General/navigation.css @@ -7,10 +7,15 @@ --nav-p-padding-left: 1em; --nav-p-text-align: left; + --nav-navShow-p-height: auto; + --nav-ul-width: 60%; --nav-ul-padding: 0.5em; --nav-ul-top: 2em; --nav-ul-right: 1em; + --nav-ul-opacity: 1; + + --nav-navShow-ul-top: 0.5em; --nav-ul-li-width: calc(100% / 5); --nav-ul-li-height: 1em; @@ -29,10 +34,15 @@ --nav-p-padding-left: 0em; --nav-p-text-align: center; + --nav-navShow-p-height: 4.5vh; + --nav-ul-width: 100%; --nav-ul-padding: 0.5em 0; --nav-ul-top: 3em; --nav-ul-right: 0em; + --nav-ul-opacity: 0; + + --nav-navShow-ul-top: 2em; --nav-ul-li-width: 100%; --nav-ul-li-height: 2em; @@ -46,10 +56,42 @@ overflow: var(--nav-overflow); color: var(--font-color-normal); - height: calc(1vh + var(--nav-p-height)); + height: calc(var(--nav-p-height)); width: 100vw; position: fixed; + + top: 0em; + + transition: top 0.5s, height 0.5s; + + z-index: 1000; +} + +#box_navBar.navShow { + + height: calc(1vh + var(--nav-navShow-p-height)); + + top: 0em; +} + +#box_navBar.navShow p { + + background: rgba(255, 255, 255, 0.75); + box-shadow: 0.05em 0.05em 0.05em #999; + font-size: var(--font-size-2_5em); + height: var(--nav-navShow-p-height); + line-height: var(--nav-navShow-p-height); +} + +#box_navBar.navShow ul { + + top: var(--nav-navShow-ul-top); +} + +#box_navBar.navHide { + + top: -6em; } #box_navBar p { @@ -65,8 +107,8 @@ width: 100%; padding-top: 0.1em; - background: rgba(255, 255, 255, 0.5); - box-shadow: 0.05em 0.05em 0.05em #999; + + transition: all 0.5s; } #box_navBar ul { @@ -80,6 +122,9 @@ right: var(--nav-ul-right); list-style-type: none; + opacity: var(--nav-ul-opacity); + + transition: top 0.5s, opacity 0.5s; } #box_navBar ul li { @@ -107,3 +152,100 @@ #box_navBar ul li:active { transform: translateY(var(--nav-ul-li-active-top)) ! important; } + +#box_navBar.nav_MenuExpand { + height: 100vh; +} + +#box_navBar.nav_MenuExpand p { + height: 100vh; + backdrop-filter: blur(0.15em); +} + +#box_navBar.nav_MenuExpand ul { + opacity: 1; +} + +#box_navBar #nav_Menu { + + position: absolute; + + display: none; + + width: var(--nav-navShow-p-height) ; + height: var(--nav-navShow-p-height); + opacity: 0; + right: 0.5em; + + transition: 1s; + z-index: 10; + + @starting-style { + opacity: 1; + } +} + +#box_navBar.navShow #nav_Menu { + + position: absolute; + + display: var(--display-phone); + + opacity: 1; + + transition: 1s; + + @starting-style { + opacity: 0; + } +} + +svg { + transition: transform 500ms cubic-bezier(0.4, 0, 0.2, 1); +} +path { + transition: transform 500ms cubic-bezier(0.4, 0, 0.2, 1), stroke-dasharray 500ms cubic-bezier(0.4, 0, 0.2, 1), stroke-dashoffset 500ms cubic-bezier(0.4, 0, 0.2, 1); +} +path:nth-child(1) { + transform-origin: 36% 40%; +} +path:nth-child(2) { + stroke-dasharray: 29 299; +} +path:nth-child(3) { + transform-origin: 35% 63%; +} +path:nth-child(4) { + stroke-dasharray: 29 299; +} +path:nth-child(5) { + transform-origin: 61% 52%; +} +path:nth-child(6) { + transform-origin: 62% 52%; +} + +.nav_MenuClick svg { + transform: rotate(90deg); +} +.nav_MenuClick path:nth-child(1) { + transform: translateX(9px) translateY(1px) rotate(45deg); +} +.nav_MenuClick path:nth-child(2) { + stroke-dasharray: 225 299; + stroke-dashoffset: -72px; +} +.nav_MenuClick path:nth-child(3) { + transform: translateX(9px) translateY(1px) rotate(-45deg); +} +.nav_MenuClick path:nth-child(4) { + stroke-dasharray: 225 299; + stroke-dashoffset: -72px; +} +.nav_MenuClick path:nth-child(5) { + transform: translateX(9px) translateY(1px) rotate(-45deg); +} +.nav_MenuClick path:nth-child(6) { + transform: translateX(9px) translateY(1px) rotate(45deg); +} + diff --git a/CascadingStyleSheets/home/home.css b/CascadingStyleSheets/home/home.css new file mode 100644 index 0000000..307eb71 --- /dev/null +++ b/CascadingStyleSheets/home/home.css @@ -0,0 +1,114 @@ + +:root { + + --home-font-size-welcome: 3em; + + --home-my-name-margin: 0.8em; + --home-my-status-margin: 3em; + + --home-content-title-margin: 1em; +} + +@media screen and (max-width: 750px) { + + :root { + + --home-font-size-welcome: 0.80em; + + --home-my-name-margin: 0.3em; + --home-my-status-margin: 1.2em; + + --home-content-title-margin: .5em; + } +} + +@keyframes home_entrance_1 { + + from { + + transform: translateY(calc(1.5 * var(--home-my-name-margin))); + opacity: 0; + } + + to { + + transform: rotateY(0); + opacity: 1; + } + +} + +#box_HomePage > div:first-child { + + margin-bottom: calc(2 * var(--home-my-name-margin)); + + width: 100vw; + height: 100vh; + + background: url(/File/Image/Home/home-background.avif); + background-size: cover; + background-position: top center; +} + +#box_HomePage > div:first-child > p { + + font-size: var(--home-font-size-welcome); + font-family: var(--fontface-content-2); + + color: var(--font-color-normal); + + width: 100vw; + height: 100vh; + line-height: 100vh; + + text-align: center; +} + +#box_HomePage > .home_MyName { + + font-size: var(--font-size-4em); + color: var(--font-color-title); + font-family: var(--fontface-title); + margin-left: var(--home-my-name-margin); + + animation: home_entrance_1 1s linear; + animation-timeline: view(85% 0); +} + +#box_HomePage > .home_MyStatus { + + font-size: var(--font-size-normal); + color: var(--font-color-light-normal); + font-family: var(--fontface-content-3); + margin-right: var(--home-my-status-margin); + + text-align: right; + + animation: home_entrance_1 1s linear; + animation-timeline: view(85% 0); +} + +#box_HomePage .home_ContentSection { + position: relative; +} + +#box_HomePage .home_SectionTitle { + + font-size: var(--font-size-3em); + font-family: var(--fontface-title-2); + margin-left: var(--home-content-title-margin); + margin-top: var(--home-content-title-margin); + color: var(--font-color-light-normal); +} + +#box_HomePage .home_ContentSectionAnimation { + + animation: home_entrance_1 1s linear; + animation-timeline: view(85% 0); +} + +#box_HomePage p.home_SectionTitle_Sticky { + + position: sticky; + top: 1em; +} diff --git a/CascadingStyleSheets/index/index.css b/CascadingStyleSheets/index/index.css index 81e5de3..99cd22f 100644 --- a/CascadingStyleSheets/index/index.css +++ b/CascadingStyleSheets/index/index.css @@ -14,4 +14,35 @@ html, body { height: 0; line-height: 0; font-size: 1px; +} + +#contentArea { + height: 100vh; + width: 100vw; + overflow: hidden overlay; +} + +#contentArea::-webkit-scrollbar { + width: 0; + background: rgba(0, 0, 0, 0); + + transition: all 0.5s; +} + +#contentArea.scrollBarNotDisplay::-webkit-scrollbar { + width: 0; + background: rgba(0, 0, 0, 0); +} + +#contentArea::-webkit-scrollbar-thumb { + background: #888; + border-radius: 1em; +} + +#contentArea::-webkit-scrollbar-track { + background-color: rgba(0, 0, 0, 0); +} + +#contentArea::-webkit-scrollbar-track-piece { + background-color: rgba(0, 0, 0, 0); } \ No newline at end of file diff --git a/File/Font/cangefeibaiw02.ttf b/File/Font/cangefeibaiw02.ttf new file mode 100644 index 0000000..e09416f Binary files /dev/null and b/File/Font/cangefeibaiw02.ttf differ diff --git a/File/Font/chengmingshouxieti.ttf b/File/Font/chengmingshouxieti.ttf new file mode 100644 index 0000000..4b46cd2 Binary files /dev/null and b/File/Font/chengmingshouxieti.ttf differ diff --git a/File/Font/shoujinti.ttf b/File/Font/shoujinti.ttf index dc9b760..50753cf 100644 Binary files a/File/Font/shoujinti.ttf and b/File/Font/shoujinti.ttf differ diff --git a/File/Icon/menu.svg b/File/Icon/menu.svg new file mode 100644 index 0000000..1f5a888 --- /dev/null +++ b/File/Icon/menu.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/File/Image/Home/home-background.avif b/File/Image/Home/home-background.avif new file mode 100644 index 0000000..e15b009 Binary files /dev/null and b/File/Image/Home/home-background.avif differ diff --git a/JavaScript/General/VirtualDOM.js b/JavaScript/General/VirtualDOM.js index ea62890..150a1e3 100644 --- a/JavaScript/General/VirtualDOM.js +++ b/JavaScript/General/VirtualDOM.js @@ -79,7 +79,7 @@ export function vNodeDiff(oldNode, newNode) { export function vNodePatch(parent, patches, index = 0) { - const el = parent.childNodes[index]; + const el = parent.children[index]; patches.forEach(patch => { switch(patch.type) { diff --git a/JavaScript/General/deepCopy.js b/JavaScript/General/deepCopy.js new file mode 100644 index 0000000..9222fc4 --- /dev/null +++ b/JavaScript/General/deepCopy.js @@ -0,0 +1,26 @@ + +export function deepCopy(obj) { + if (obj === null || typeof obj !== 'object') { + return obj; + } + + if (obj instanceof Date) { + return new Date(obj); + } + + if (Array.isArray(obj)) { + const arrCopy = []; + for (let i = 0; i < obj.length; i++) { + arrCopy[i] = deepCopy(obj[i]); + } + return arrCopy; + } + + const objCopy = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + objCopy[key] = deepCopy(obj[key]); + } + } + return objCopy; +} diff --git a/JavaScript/General/loading.js b/JavaScript/General/loading.js index 5a2ced9..512be65 100644 --- a/JavaScript/General/loading.js +++ b/JavaScript/General/loading.js @@ -1,3 +1,5 @@ + +import { deepCopy } from "./deepCopy.js"; import { vNodeCreate, vNodeRender, vNodeDiff, vNodePatch } from "./VirtualDOM.js"; export function initLoading() { @@ -8,21 +10,65 @@ export function initLoading() { }, [ vNodeCreate("li", { class: "waiting_to_insert" - }, ["Hello World!"]) + }, ["Hello World!"]), + + vNodeCreate("p", { + id: "progressBar" + }, ["0%"]), ]), ]); } -export function pageLoading() { +export function loadingMessage(boxLoading, loadingContent, message, delay) { + return new Promise(resolve => { + setTimeout(() => { + var oldLoadingContent = deepCopy(loadingContent); + delete loadingContent.children[1].children.slice(-2)[0].props.class; + loadingContent.children[1].children.splice(-1, 0, vNodeCreate("li", { + class: "waiting_to_insert" + }, [message])); + vNodePatch(boxLoading, vNodeDiff(oldLoadingContent, loadingContent)); + resolve(loadingContent); + }, delay); + }); +} + +export function updateProgress(progress) { + document.getElementById("progressBar").innerText = progress; +} + +export async function pageLoading() { const boxLoading = document.getElementById("box_loading"); var loadingContent = initLoading(); boxLoading.appendChild(vNodeRender(loadingContent)); + + const message = [ + "你好,世界!", + "", + "Loading ...", + "加载中 。。。", + "0%" + ]; + for (const msg of message) { + loadingContent = await loadingMessage(boxLoading, loadingContent, msg, 500); + } + + updateProgress("10%"); } export function pageLoaded() { const boxLoading = document.getElementById("box_loading"); - -} \ No newline at end of file + const childLoading = boxLoading.querySelector("div"); + updateProgress("100%"); + boxLoading.classList.add("hide"); + + document.querySelector("#box_navBar").classList.remove("navHide"); + + setTimeout(() => { + boxLoading.removeChild(childLoading); + console.log(window.globalValues.currentVDom); + }, 1000); +} diff --git a/JavaScript/General/navigation.js b/JavaScript/General/navigation.js index d37b0c8..1833891 100644 --- a/JavaScript/General/navigation.js +++ b/JavaScript/General/navigation.js @@ -4,37 +4,38 @@ import { vNodeCreate, vNodeRender } from "./VirtualDOM.js"; export function renderNavigation() { - console.log(window.globalValues.translateData); + const navigationContent = window.globalValues.translateData.navigation[window.globalValues.language]; + console.log(navigationContent); let domNav = vNodeCreate("div", {}, [ + vNodeCreate("p", {}, [window.globalValues.translateData.index[window.globalValues.language]._title_]), vNodeCreate("ul", {}, [ vNodeCreate("li", { onclick: "window.clickEvent.navigationMenuClick(this, '/')", class: "selected" - }, ["Home"]), + }, [navigationContent._home_]), vNodeCreate("li", { onclick: "window.clickEvent.navigationMenuClick(this, '/travel')", - }, ["Travel"]), + }, [navigationContent._travel_]), vNodeCreate("li", { onclick: "window.clickEvent.navigationMenuClick(this, '/lens')", - }, ["Lens"]), + }, [navigationContent._lens_]), vNodeCreate("li", { onclick: "window.clickEvent.navigationMenuClick(this, '/projects')", - }, ["Projects"]), + }, [navigationContent._projects_]), vNodeCreate("li", { onclick: "window.clickEvent.navigationMenuClick(this, '/about')", - }, ["About"]), + }, [navigationContent._about_]), ]), ]); - window.globalValues.currentVDom = domNav; - document.getElementById("box_navBar").appendChild(vNodeRender(window.globalValues.currentVDom)); + document.getElementById("box_navBar").appendChild(vNodeRender(domNav)); } export function navigationMenuClick(obj, path) { @@ -43,4 +44,10 @@ export function navigationMenuClick(obj, path) { obj.classList.add("selected"); route(path); -} \ No newline at end of file +} + +export function navigationMenuExpand(obj) { + + obj.classList.toggle("nav_MenuClick"); + document.querySelector("#box_navBar").classList.toggle("nav_MenuExpand"); +} diff --git a/JavaScript/Home/home.js b/JavaScript/Home/home.js new file mode 100644 index 0000000..c1d6969 --- /dev/null +++ b/JavaScript/Home/home.js @@ -0,0 +1,83 @@ + +export function homeScroll() { + + const homeContent = document.querySelector("#contentArea"); + console.log(homeContent); + homeContent.addEventListener("scroll", function(event) { + + const homeSectionTitleTag = document.querySelectorAll(".home_SectionTitle"); + homeSectionTitleTag.forEach(target => { + const rect = target.getBoundingClientRect(); + const isInViewport = rect.top >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight); + if (isInViewport) { + + if (rect.top <= (document.querySelector("body").clientHeight / 3)) { + target.classList.remove("home_ContentSectionAnimation"); + target.classList.add("home_SectionTitle_Sticky"); + } else { + target.classList.add("home_ContentSectionAnimation"); + target.classList.remove("home_SectionTitle_Sticky"); + } + } + }); + + if (event.target.scrollTop >= document.querySelector("#box_navBar div p").clientHeight) { + document.querySelector("#box_navBar").classList.add("navShow"); + } else if (event.target.scrollTop <= document.querySelector("#box_navBar div p").clientHeight) { + document.querySelector("#box_navBar").classList.remove("navShow"); + } + + if (event.target.scrollTop >= document.querySelector("body").clientHeight) { + this.classList.remove("scrollBarNotDisplay"); + } else if (event.target.scrollTop <= document.querySelector("body").clientHeight) { + this.classList.add("scrollBarNotDisplay"); + } + }); +} + +// export function homeSectionTitlePosition() { + +// const homeSectionTitleTag = document.querySelectorAll(".home_ContentSectionAnimation"); + +// const intersectionObserver = new IntersectionObserver((entries, observer) => { +// entries.forEach(entry => { +// if (entry.isIntersecting) { +// trackPosition(entry.target); +// } else { +// stopTrackingPosition(entry.target); +// } +// }); +// }, { +// threshold: 0.2 +// }); + +// homeSectionTitleTag.forEach(target => { +// intersectionObserver.observe(target); +// }); + +// const homeSectionPosition = new Map(); +// function trackPosition(element) { + +// if (homeSectionPosition.has(element)) return; + +// function updatePosition() { + +// const rect = element.getBoundingClientRect(); +// console.log(element); +// console.log(`Element ${element} Position -> Top: ${rect.top}, Left: ${rect.left}`); + +// if (element.isConnected) { +// homeSectionPosition.set(element, requestAnimationFrame(updatePosition)); +// } +// } + +// homeSectionPosition.set(element, requestAnimationFrame(updatePosition)); +// } + +// function stopTrackingPosition(element) { +// if (homeSectionPosition.has(element)) { +// cancelAnimationFrame(homeSectionPosition.get(element)); +// homeSectionPosition.delete(element); +// } +// } +// } \ No newline at end of file diff --git a/JavaScript/Index/index.js b/JavaScript/Index/index.js index 0587651..ce88176 100644 --- a/JavaScript/Index/index.js +++ b/JavaScript/Index/index.js @@ -4,8 +4,9 @@ import { getJson } from "../General/getJson.js"; import { userLanguage } from "../General/language.js"; // import { createElement, render, vDOMPatch, vNodeDiff } from "../General/VirtualDOM.js"; -import { navigationMenuClick, renderNavigation } from "../General/navigation.js"; +import { navigationMenuClick, navigationMenuExpand, renderNavigation } from "../General/navigation.js"; import { pageLoaded, pageLoading } from "../General/loading.js"; +import { homeScroll } from "../Home/home.js"; window.globalValues = { @@ -16,6 +17,7 @@ window.globalValues = { } window.clickEvent = {} +window.webWorker = {} window.addEventListener("DOMContentLoaded", () => { @@ -33,10 +35,13 @@ window.onload = async function() { document.title = window.globalValues.translateData.index[window.globalValues.language]._title_; + window.clickEvent.navigationMenuExpand = navigationMenuExpand; window.clickEvent.navigationMenuClick = navigationMenuClick; renderNavigation(); - console.log(window.globalValues.currentVDom); + + // Home + homeScroll(); await testLoad(); pageLoaded(); @@ -54,6 +59,9 @@ async function getData() { try { var translateIndex = await getJson("/Language/Index/index.json"); window.globalValues.translateData.index = translateIndex; + + var translateNavigation = await getJson("/Language/General/navigation.json"); + window.globalValues.translateData.navigation = translateNavigation; } catch (err) { console.error(err); } diff --git a/Language/General/navigation.json b/Language/General/navigation.json index 7816a7e..837c927 100644 --- a/Language/General/navigation.json +++ b/Language/General/navigation.json @@ -4,7 +4,7 @@ "_travel_": "我的旅行", "_lens_": "我的方框", "_projects_": "我的项目", - "_settings_": "网页设置" + "_about_": "关于我的" }, "en": { @@ -12,6 +12,6 @@ "_travel_": "Travel", "_lens_": "Lens", "_projects_": "Projects", - "_settings_": "Settings" + "_about_": "About" } } \ No newline at end of file diff --git a/Language/Index/index.json b/Language/Index/index.json index c23eb49..a7ad554 100644 --- a/Language/Index/index.json +++ b/Language/Index/index.json @@ -1,9 +1,11 @@ { "zh": { - "_title_": "我的网页" + "_title_": "SEECHEN", + "_welcome_": "花径不曾缘客扫,蓬门今始为君开." }, "en": { - "_title_": "My Website" + "_title_": "SEECHEN", + "_welcome_": "Delighted to have the honor of your visit." } } \ No newline at end of file diff --git a/_index.html b/_index.html index 7aee260..0d18330 100644 --- a/_index.html +++ b/_index.html @@ -19,15 +19,21 @@ + + + + + + @@ -36,13 +42,110 @@
+
-
+ -
+
+ +
+
+

Delighted to have the honor of your visit.

+ +
+ +

HERE'S

+

SEECHEN LEE.

+ + +

LOOKING for WORK.

+ + +
+

Education

+ +

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+
+
+

Projects

+ +

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+
+ +

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+

a

+ +
+ +
+
\ No newline at end of file