diff --git a/src/static_view.rs b/src/static_view.rs index b8b393f234fb83b5975bcaff9a77973241aed583..41e3d63c4c22280990e120e3bc35a6fb5ccb1b1d 100644 --- a/src/static_view.rs +++ b/src/static_view.rs @@ -69,11 +69,12 @@ pub fn create_static_view(app_state: &AppState, article: &Article) -> Result<(), <link rel='icon' type='image/svg+xml' href='/favicon.svg' /> <title>{title}</title> <link href='/style/style.css' rel='stylesheet' /> + <script type='application/json' id='article-json'>{art_json}</script> </head> <body> - <main data='{art_json}'></main> - <h1 style='visibility:hidden;'>{title}</h1> - <h2 style='visibility:hidden;'>{subtitle}</h2> + <h1 class='placeholder'>{title}</h1> + <h2 class='placeholder'>{subtitle}</h2> + <p class='placeholder'>{description}</p> </body> <script type='text/javascript' src='/article-view.js'></script> </html> diff --git a/website/src/article-vew-components/game-article.js b/website/src/article-vew-components/game-article.js new file mode 100644 index 0000000000000000000000000000000000000000..f0f757e4bb118f6ac81d9b0ca9d4316e651bf97e --- /dev/null +++ b/website/src/article-vew-components/game-article.js @@ -0,0 +1,182 @@ +"use strict"; + +const ImageCarousel = require("../generic-components/image-carousel"); +const { getArticleBody } = require("../lib/article-utils"); +const { fetch_json_or_error_text } = require("../lib/fetch"); +const { MentaloEngine } = require("mentalo-engine"); +const translator = require("ks-cheap-translator"); +const { images_url, data_url } = require("../../constants"); +const t = translator.trad.bind(translator); + + +class GameArticle { + constructor(data) { + this.data = data; + this.parse_body(); + } + + parse_body() { + let body = getArticleBody(this.data.body); + const play_btn_regex = /\[PLAY_BUTTON\s\{.+\}\]/g; + const found_play_buttons = body.match(play_btn_regex); + if (found_play_buttons) { + this.build_play_button(JSON.parse(found_play_buttons[0].replace(/[\[\]PLAY_BUTTON\s]/g, ""))); + body = body.replace(play_btn_regex, ""); + } + this.body = body; + } + + build_play_button(button_data) { + this.render_play_button = { + tag: "button", + class: "play-button", + contents: "â–¶ï¸ " + t("Jouer"), + onclick: this.handle_click_play.bind(this, button_data.filename, button_data.engine) + }; + } + + load_and_run_mentalo_game(filename, button_element) { + const button_text = button_element.innerHTML; + button_element.innerHTML = "Loading ..."; + button_element.style.pointerEvents = "none"; + + fetch_json_or_error_text(`${data_url}/${filename}`) + .then(game_data => { + const container = document.createElement("div"); + container.style.position = "fixed"; + container.style.top = 0; + container.style.left = 0; + container.style.right = 0; + container.style.bottom = 0; + container.style.zIndex = 10; + container.style.display = "flex"; + container.style.justifyContent = "center"; + container.style.alignItems = "center"; + + container.id = "kuadrado-tmp-game-player-container"; + document.body.appendChild(container); + document.body.style.overflow = "hidden"; + + const engine = new MentaloEngine({ + game_data, + fullscreen: true, + frame_rate: 30, + container, + on_quit_game: () => { + container.remove(); + document.body.style.overflow = "visible"; + } + }); + + engine.init(); + engine.run_game(); + }) + .catch(err => console.log(err)) + .finally(() => { + button_element.innerHTML = button_text; + button_element.style.pointerEvents = "unset"; + }); + } + + handle_click_play(filename, engine, e) { + switch (engine) { + case "mentalo": + this.load_and_run_mentalo_game(filename, e.target); + break; + default: + console.log("Error, unkown engine") + return; + } + } + + render() { + const { + images, + details, + title, + subtitle, + } = this.data; + + return { + tag: "article", + class: "game-article", + contents: [ + { + tag: "div", + id: "article-banner", + style_rules: { + backgroundImage: `url(${images_url}/${images[0]})`, + }, + contents: [ + { + tag: "div", + id: "header-text-container", + contents: [ + { + tag: "h1", + contents: title, + class: "header-text h1", + }, + { + tag: "h2", + contents: subtitle, + class: "header-text h2", + }, + this.render_play_button, + ] + } + ] + }, + { + tag: "div", + id: "article-contents", + contents: [ + { + tag: "div", + id: "article-body", + contents: [ + { + tag: "p", + contents: this.body + } + ], + }, + new ImageCarousel({ images: images.map(img => `${images_url}/${img}`) }).render(), + details.length > 0 && { + tag: "div", + class: "article-details", + contents: [ + { + tag: "h2", + contents: "Details", + }, + { + tag: "ul", + class: "details-list", + contents: details.map(detail => { + return { + tag: "li", + class: "detail", + contents: [ + { tag: "label", contents: detail.label }, + { + tag: "div", + class: "detail-value", + contents: detail.value + }, + ], + }; + }), + }, + ], + }, + ] + }, + + + ], + }; + } +} + +module.exports = GameArticle; diff --git a/website/src/article-view.js b/website/src/article-view.js index 60613f1d18eb43e4a0622fca157a68e5d188513d..3ea0a59e1716c80d7c8e63e540221f895542ee05 100644 --- a/website/src/article-view.js +++ b/website/src/article-view.js @@ -1,18 +1,33 @@ "use strict"; +const GameArticle = require("./article-vew-components/game-article"); const WebPage = require("./lib/web-page"); const runPage = require("./run-page"); class ArticlePage extends WebPage { constructor() { super({ id: "article-page" }); - this.article = JSON.parse(document.querySelector("main").getAttribute("data")) + this.article = JSON.parse(document.getElementById("article-json").innerHTML); + } + + render_article() { + switch (this.article.category) { + case "games": + return new GameArticle(this.article).render(); + case "education": + return; + case "software": + return; + } } render() { return { - tag: "h1", - contents: this.article.title, + tag: "div", + id: "article-view-container", + contents: [ + this.render_article(), + ], } } } diff --git a/website/src/article-view.scss b/website/src/article-view.scss new file mode 100644 index 0000000000000000000000000000000000000000..d1a549703e3a4d8757723c6bbc1a28cfac3483f2 --- /dev/null +++ b/website/src/article-view.scss @@ -0,0 +1,91 @@ +#article-view-container { + position: relative; + + article { + background-image: $wp_bin; + + &.game-article { + #article-banner { + width: 100%; + height: 500px; + background-size: cover; + background-position: center center; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + position: relative; + + #header-text-container { + position: absolute; + width: 100%; + height: 100%; + background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .5)); + padding: 50px; + display: flex; + flex-direction: column; + justify-content: flex-end; + + .header-text { + color: #ffffffbb; + margin: 0; + + &.h1 { + font-size: 100px; + margin: 0; + } + + &.h2 { + font-style: italic; + margin: 0 0 0 150px; + font-size: 32px; + } + } + + .play-button { + position: absolute; + right: 10px; + bottom: 10px; + padding: 20px 40px; + border: 1px solid; + border-radius: 10px; + background-color: unset; + font-weight: bold; + font-size: 20px; + cursor: pointer; + color: $blue_2; + + &:hover { + color: $blue_3; + } + } + } + } + + #article-contents { + // background-color: #1f2122; + // color: $light_1; + + // * { + // color: $light_1; + // } + background-color: white; + padding: 40px; + display: grid; + grid-template-columns: 1fr 500px; + max-width: 1000px; + margin: 0 auto; + + #article-body { + p { + margin: 0; + } + } + + .image-carousel { + height: 400px; + } + } + } + } + +} \ No newline at end of file diff --git a/website/src/homepage.scss b/website/src/homepage.scss index dc162ea95646945d62ddd22a40a98f7714723932..61d150e9c65866e818c2079d6b5d3b245179049d 100644 --- a/website/src/homepage.scss +++ b/website/src/homepage.scss @@ -1,44 +1,57 @@ #home-page { display: flex; flex-direction: column; + .section-title { padding: 10px; margin: 0; color: $light_2; } + .page-header { .philo-bubbles { @include flex-center; flex-wrap: wrap; gap: 40px; + @media screen and (max-width: $screen_m) { gap: 20px; } + margin: 30px 20px; + li { border-radius: 100%; @include flex-center; background-color: $light_0; + * { color: $medium_grey; } + width: 100px; height: 100px; + @media screen and (max-width: $screen_s) { width: 75px; height: 75px; + * { font-size: 12px; } } + &:first-child { background-color: $medium_grey; + * { color: white; } } + &:last-child { background-color: $dark_1; + * { color: $light_1; } @@ -52,17 +65,20 @@ grid-template-columns: 1fr 1fr 1fr; gap: 30px; padding: 100px 0; + .theme-card { display: flex; flex-direction: column; width: 100%; cursor: pointer; + .card-img { width: 100%; height: 240px; overflow: hidden; @include flex-center-col; position: relative; + img { position: absolute; max-width: 100%; @@ -70,6 +86,7 @@ padding: 10px; } } + .card-title { h2 { margin: 0; @@ -80,16 +97,20 @@ background-color: white; } } + .card-description { flex: 1; padding: 30px 20px; + p { margin: 0; color: $blue_2; text-align: center; } } + transition: transform 0.3s; + &:hover { transform: scale(1.03); } @@ -97,30 +118,36 @@ } .kuadrado-values { - background-image: url("/assets/images/wallpaper_binary_light.png"); + background-image: $wp_bin_light; padding: 100px 0 120px; + *:not(a, blue) { color: $light_0; } + h2 { @include flex-center; margin: 0 auto 60px; width: 120px; height: 120px; - background-image: url("/assets/images/wallpaper_binary.png"); + background-image: $wp_bin; border-radius: 100%; color: $blue_3; } + ul.values-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; + li { - background-image: url("/assets/images/wallpaper_binary.png"); + background-image: $wp_bin; padding: 30px 20px 40px; + h3 { text-align: center; } + p { text-align: justify; } @@ -132,23 +159,29 @@ .poles { grid-template-columns: 1fr; gap: 40px; + .theme-card { .card-img { height: 300px; + img { min-width: unset; height: 100%; } } + .card-title { h2 { padding: 5px 20px; } } + .card-description { padding: 20px 30px; } + transition: transform 0.3s; + &:hover { transform: none; } @@ -161,12 +194,14 @@ } } } + @media screen and (max-width: $page_contents_center_width) { .poles { padding: 20px; } + .articles-displayer { padding: 0; } } -} +} \ No newline at end of file diff --git a/website/src/pages/games/games.scss b/website/src/pages/games/games.scss index d0dd713acbf557f59a2eee438374ce4877413c58..d06b1586df318b1305215460ec9d56da78780ef1 100644 --- a/website/src/pages/games/games.scss +++ b/website/src/pages/games/games.scss @@ -5,9 +5,11 @@ grid-template-columns: 0.7fr 1fr; gap: 30px 50px; margin: 20px 0; + &.game-article { grid-template-rows: repeat(7, auto); width: 100%; + .game-title { grid-column: 1 / span 2; margin: 0; @@ -23,6 +25,7 @@ background-color: black; overflow: hidden; height: 300px; + img { width: 100%; } @@ -33,6 +36,7 @@ gap: 10px; flex-wrap: wrap; margin: 10px 20px; + span { font-size: 12px; padding: 4px; @@ -48,6 +52,7 @@ margin: 10px 20px; color: $medium_grey; } + .game-description { grid-column: 1; text-align: justify; @@ -60,16 +65,21 @@ height: 400px; } } + &.placeholder { * { background-color: $light_0; } + height: 400px; } + @media screen and (max-width: $screen_l) { grid-template-columns: 1fr; + &.game-article { grid-template-rows: repeat(6, auto); + .game-title { grid-column: 1; padding: 0; @@ -89,6 +99,7 @@ } } } + .play-button { border: none; background-color: unset; @@ -96,10 +107,11 @@ font-size: 20px; cursor: pointer; color: $blue_2; + &:hover { color: $blue_3; } } } } -} +} \ No newline at end of file diff --git a/website/src/run-page.js b/website/src/run-page.js index 62a540225157626043a625169f25d76963901a66..233bd9cad3ee5d67ac06618b24d788ff62f010e6 100644 --- a/website/src/run-page.js +++ b/website/src/run-page.js @@ -1,11 +1,13 @@ "use strict"; -const renderer = require("object-to-html-renderer") +const renderer = require("object-to-html-renderer"); const Template = require("./template/template"); + module.exports = function runPage(PageComponent) { + document.querySelectorAll(".placeholder").forEach(e => e.remove()) + renderer.register("obj2htm"); const template = new Template({ page: new PageComponent() }); - renderer.register("obj2htm") obj2htm.setRenderCycleRoot(template); obj2htm.renderCycle(); }; diff --git a/website/src/style.scss b/website/src/style.scss index d523c3b1c5cf1ad8d95e59a0db629dc2e449f9c8..9f4e0434af77a335b8fd3efcb35ac247e9813083 100644 --- a/website/src/style.scss +++ b/website/src/style.scss @@ -6,16 +6,22 @@ body { color: $dark_1; line-height: 1.3em; } - font-family: Arial, Helvetica, sans-serif; + + font-family: Arial, + Helvetica, + sans-serif; margin: 0; + ul { margin: 0; padding: 0; list-style-type: none; } + a { color: $blue_2; text-decoration: none; + &:hover { color: $blue_3; } @@ -24,12 +30,15 @@ body { blue { color: $blue_2; } + red { color: $red_1; } + green { color: $kaki; } + emoji { font-style: initial; font-size: 25px; @@ -68,6 +77,7 @@ main { height: 40px; padding: 20px 10%; @include flex-center; + strong { font-size: 18px; color: $blue_1; @@ -79,18 +89,21 @@ main { @include flex-center; background-color: black; position: relative; + img { position: absolute; object-fit: contain; height: 80%; max-width: 100%; } + .carousel-bullets { position: absolute; bottom: 0; padding: 20px; display: flex; gap: 10px; + .bullet { cursor: pointer; width: 8px; @@ -98,14 +111,17 @@ main { background-color: $medium_grey; border-radius: 100%; box-shadow: 0 0 3px black; + &.active { background-color: $light_0; } } } + @media screen and (max-width: $screen_l) { .carousel-bullets { gap: 30px; + .bullet { width: 12px; height: 12px; @@ -121,26 +137,32 @@ main { position: -webkit-sticky; top: 0; z-index: 10; + nav { display: flex; align-items: center; height: $navbar_height; + .home { margin: 0 10px; + a { display: flex; align-items: center; gap: 10px; + img { height: 40px; width: auto; } + img.logo-text { width: 120px; height: auto; } } } + ul { display: flex; padding: 0; @@ -148,8 +170,10 @@ main { list-style-type: none; height: 100%; flex: 1; + li { position: relative; + a { display: flex; align-items: center; @@ -173,12 +197,14 @@ main { background-color: white; white-space: nowrap; } + &.active { a { color: $dark_2; border-bottom: 3px solid; } } + &:hover { a { color: $dark_2; @@ -187,10 +213,12 @@ main { .submenu { visibility: unset; max-height: 1000px; + a { color: $light_1; border: none; } + li { &:hover { a { @@ -200,17 +228,20 @@ main { } } } + &.lang-flags { display: flex; align-items: center; gap: 10px; margin-left: auto; padding: 0 20px; + img { width: 35px; height: 30px; cursor: pointer; opacity: 0.5; + &.selected, &:hover { opacity: 1; @@ -219,11 +250,14 @@ main { } } } + .burger { display: none; } + @media screen and (max-width: $screen_s) { justify-content: space-between; + .burger { @include flex-center-col; font-weight: bold; @@ -236,8 +270,10 @@ main { color: $dark_3; font-size: 25px; } + ul { display: none; + &.responsive-show { display: flex; flex-direction: column; @@ -249,12 +285,14 @@ main { background-color: white; box-shadow: 0 4px 6px 2px #0000000a; height: unset; + li { &.active { a { border: none; } } + .submenu { display: flex; visibility: visible; @@ -264,6 +302,7 @@ main { transition: max-height 0.6s; top: unset; left: unset; + li { a { font-weight: 400; @@ -271,8 +310,10 @@ main { color: $light_1; } } + margin-left: 20px; } + &.lang-flags { margin-left: unset; justify-content: space-around; @@ -284,83 +325,103 @@ main { } } } + #page-container { width: 100%; flex: 1; + .page-header { - background-image: url("/assets/images/wallpaper_binary.png"); + background-image: $wp_bin; padding: 50px 0; + h1 { padding: 15px 40px 0; font-size: 25px; color: $blue_2; margin: 0 auto; } + p { color: $blue_3; + * { color: $blue_3; } + font-style: italic; padding: 15px 40px 15px 100px; margin: 0 auto; font-size: 18px; } + .big-logo { @include flex-center; gap: 20px; padding: 20px; + img { width: 200px; max-width: 100%; + &.logo-text { width: 300px; max-width: 100%; } } } + .logo { padding-left: 30px; @include flex-center; + img { width: 100%; } } + @media screen and (max-width: $screen_s) { h1 { padding: 15px 20px 0; } + p { padding: 20px 20px 30px 40px; text-align: justify; } + .big-logo { flex-direction: column; } } + &.logo-left { .grid-wrapper { h1 { width: 100%; } + display: grid; grid-template-columns: 120px 1fr; grid-template-rows: auto 1fr; + .logo { grid-column: 1; grid-row: 1; width: 100%; } + p { margin: 0; grid-column: 1 / span 2; } } + @media screen and (max-width: $screen_m) { .grid-wrapper { h1 { padding: 0 20px; } + .logo { padding: 0 20px; } @@ -368,89 +429,108 @@ main { } } } + .page-philo { - background-image: url("/assets/images/wallpaper_binary.png"); + background-image: $wp_bin; padding: 120px 30px; + p { width: 100%; max-width: 600px; font-size: 18px; color: $light_2; + * { color: $light_2; } + text-align: center; font-style: italic; font-weight: bold; } } + .page-contents-center { @include page-contents-center; } + h2.page-section-title { color: $blue_2; padding: 20px 0 10px; @include page-contents-center; } - .article-details { - grid-column: 1 / span 2; - h2 { - color: $medium_grey; - margin: 0 10px; - padding: 10px 0 0; - font-size: 16px; - } - ul.details-list { - margin: 10px; - .detail { - display: grid; - grid-template-columns: 1fr auto; - gap: 20px; - font-size: 12px; - border-bottom: 1px solid $light_0; - padding: 5px 0; - label { - font-weight: bold; - color: $medium_grey; - white-space: nowrap; - } - .detail-value { - text-align: right; - } - } - } - } + // .article-details { + // grid-column: 1 / span 2; + + // h2 { + // color: $medium_grey; + // margin: 0 10px; + // padding: 10px 0 0; + // font-size: 16px; + // } + + // ul.details-list { + // margin: 10px; + + // .detail { + // display: grid; + // grid-template-columns: 1fr auto; + // gap: 20px; + // font-size: 12px; + // border-bottom: 1px solid $light_0; + // padding: 5px 0; + + // label { + // font-weight: bold; + // color: $medium_grey; + // white-space: nowrap; + // } + + // .detail-value { + // text-align: right; + // } + // } + // } + // } @import "./homepage.scss"; @import "./pages/education/education.scss"; @import "./pages/games/games.scss"; @import "./pages/software-development/software-development.scss"; + @import "./article-view.scss"; } + footer { @include flex-center-col; width: 100%; - background-image: url("/assets/images/wallpaper_binary.png"); + background-image: $wp_bin; padding: 40px 20px; gap: 20px; font-size: 12px; + span { color: $light_1; text-align: center; } + .logo { @include flex-center; gap: 10px; + img { width: 35px; + &.text-logo { width: 100px; } } } + .social { @include flex-center; gap: 20px; + a { background-color: $dark_3; @include flex-center; @@ -462,4 +542,4 @@ main { } } } -} +} \ No newline at end of file diff --git a/website/src/template/template.js b/website/src/template/template.js index acf1dad70da772f3aa42687ee7b80218816d34cd..e1cee3c4f6b7d0108eeeb6ec4c25cc456fa86207 100644 --- a/website/src/template/template.js +++ b/website/src/template/template.js @@ -95,7 +95,7 @@ class Template { }, { tag: "span", - contents: `Copyleft 🄯 ${new Date() + contents: `Copyleft ${new Date() .getFullYear()} Kuadrado Software | ${t("kuadrado-footer-copyleft")}`, }, diff --git a/website/src/theme.scss b/website/src/theme.scss index 8c3add6763df3b5e1488dccd1fa8c260f6502a90..9e4871b3cf64acff15f7f4edc729e9c81f22bc28 100644 --- a/website/src/theme.scss +++ b/website/src/theme.scss @@ -27,6 +27,10 @@ $screen_xs: 480px; $navbar_height: 60px; $page_contents_center_width: 1300px; +// urls +$wp_bin: url("/assets/images/wallpaper_binary.png"); +$wp_bin_light: url("/assets/images/wallpaper_binary_light.png"); + // mixins @mixin flex-center { @@ -45,7 +49,8 @@ $page_contents_center_width: 1300px; width: $page_contents_center_width; max-width: 100%; margin: 0 auto; + @media screen and (max-width: $page_contents_center_width) { padding: 20px 20px 0; } -} +} \ No newline at end of file