diff --git a/backend/src/controllers/mods.js b/backend/src/controllers/mods.js index d1f36dc..840ce08 100644 --- a/backend/src/controllers/mods.js +++ b/backend/src/controllers/mods.js @@ -43,7 +43,7 @@ async function getModByName(req, res) { try { // Query const name = req.params.name - const query_result = await mod_service.getModByName(name); + const query_result = await mod_service.getFullModInfos(name); res.json(query_result); } catch (error) { handleError(error, res); diff --git a/backend/src/models/mod.js b/backend/src/models/mod.js index f7abfd9..6bfd051 100644 --- a/backend/src/models/mod.js +++ b/backend/src/models/mod.js @@ -14,24 +14,25 @@ async function getModByName(name) { } -async function getModFullInfos(name) { +async function getFullModInfos(name) { // Query const base_infos = db.query(`SELECT * FROM Mods WHERE name = ?`, [name]); const other_infos = db.query(`SELECT full_description, license, links, creation_date, downloads_count - FROM ModInfos WHERE name = ?`, [name]); - const tags = getModTags(name); + FROM ModInfos WHERE mod = ?`, [name]); + const tags = listTags(name); - // Merge - const res = {...await base_infos, ...await other_infos, ...tags}; - - return res; + return [await base_infos, await other_infos, await tags]; } async function listVersions(mod_name) { return await db.query("SELECT * FROM ModVersions WHERE mod = ?", [mod_name]); } +async function listTags(mod_name) { + return await db.query(`SELECT tag FROM ModTags WHERE mod = ?`, [mod_name]); +} + async function getVersionByNumber(mod_name, version_number) { return await db.query(`SELECT * FROM ModVersions WHERE mod = ? @@ -184,8 +185,8 @@ async function containsTag(name, tag) { // --- Exports --- -module.exports = { getAllMods, getModByName, getModFullInfos, - listVersions, getVersionByNumber, getVersion, +module.exports = { getAllMods, getModByName, getFullModInfos, + listVersions, listTags, getVersionByNumber, getVersion, createMod, addVersion, addTags, updateMod, deleteMod, deleteVersion, deleteTags, diff --git a/backend/src/services/modService.js b/backend/src/services/modService.js index 4cd8862..758db49 100644 --- a/backend/src/services/modService.js +++ b/backend/src/services/modService.js @@ -12,7 +12,7 @@ async function getAllMods() { } async function getModByName(name) { - const res = model.getModByName(name); + const res = await model.getModByName(name); if (res.length == 0) { throw new AppError(404, "Cannot find mod with this name", "Not found"); } @@ -20,16 +20,20 @@ async function getModByName(name) { } async function getFullModInfos(name) { - const res = model.getFullModInfos(name); - if (res.length == 0) { - throw new AppError(404, "Cannot find mod with this name", "Not found"); + const [base_infos, other_infos, tags] = await model.getFullModInfos(name); + // Check + if (base_infos.length == 0 || other_infos.length === 0) { + throw new AppError(404, "Cannot find mod with this name", "Not found", "Couldn't retrieve from database correctly"); } - return res[0]; + // Merge + const mod_infos = {...other_infos[0], tags: tags} + const res = {...base_infos[0], mod_infos: mod_infos}; + return res; } async function getModVersion(infos) { const { mod, version_number, game_version, platform, environment} = infos; - const res = model.getModVersion(mod, version_number, game_version, platform, environment); + const res = await model.getModVersion(mod, version_number, game_version, platform, environment); if (res.length == 0) { throw new AppError(404, "Cannot find mod with this name", "Not found"); } @@ -51,7 +55,7 @@ async function createMod(mod_data, author) { mod_infos.full_description = await mdToHtml(mod_infos.full_description); // Convert await sanitizeModData(mod_data); // Sanitize //TODO - mod_infos.creation_date = 0 + mod_infos.creation_date = Date.now(); // Write changes to database await model.createMod(name, display_name, author, description, mod_infos); diff --git a/frontend/src/app.jsx b/frontend/src/app.jsx index 5e1bbed..b61c897 100644 --- a/frontend/src/app.jsx +++ b/frontend/src/app.jsx @@ -7,8 +7,11 @@ import { Router } from 'preact-router'; // Pages import HomePage from './pages/home'; import ModsPage from './pages/mods'; -// import AboutPage from './pages/about'; +import ModpacksPage from './pages/modpacks'; +import AboutPage from './pages/about'; import SettingsPage from './pages/settings'; +import ModPage from './pages/mod_page' +import ModCreationPage from './pages/mod_creation' // Components import NavBar from './components/NavBar/navbar' @@ -30,8 +33,12 @@ export function App() { - {/* */} + + + + + diff --git a/frontend/src/assets/example.jpg b/frontend/src/assets/example.jpg new file mode 100644 index 0000000..62eaab9 Binary files /dev/null and b/frontend/src/assets/example.jpg differ diff --git a/frontend/src/assets/favorite.svg b/frontend/src/assets/favorite.svg new file mode 100644 index 0000000..01a36da --- /dev/null +++ b/frontend/src/assets/favorite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/profile.svg b/frontend/src/assets/profile.svg new file mode 100644 index 0000000..7bc75ae --- /dev/null +++ b/frontend/src/assets/profile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Buttons/button.jsx b/frontend/src/components/Buttons/button.jsx index 99bfec9..105f156 100644 --- a/frontend/src/components/Buttons/button.jsx +++ b/frontend/src/components/Buttons/button.jsx @@ -1,18 +1,20 @@ import { h } from 'preact' // Necessary ? import styles from './button.module.css' -function Button({ children, onClick, className, variant = 'primary', ...rest}) { +function Button({ children, onClick, href, className, variant = 'primary', ...rest}) { const buttonClasses = `${styles.button} ${styles[variant] || ''} ${className || ''}` return ( - + + + ) } diff --git a/frontend/src/components/Buttons/button.module.css b/frontend/src/components/Buttons/button.module.css index eefaec6..b8688b5 100644 --- a/frontend/src/components/Buttons/button.module.css +++ b/frontend/src/components/Buttons/button.module.css @@ -13,6 +13,10 @@ transition: 300ms; } +.button:hover { + cursor: pointer; +} + .primary { background-color: #353be5; border-color: #4e53dc; diff --git a/frontend/src/components/Buttons/checkbox.jsx b/frontend/src/components/Buttons/checkbox.jsx index e69de29..3314b32 100644 --- a/frontend/src/components/Buttons/checkbox.jsx +++ b/frontend/src/components/Buttons/checkbox.jsx @@ -0,0 +1,38 @@ +//TODO made by AI +import { h } from 'preact'; +import { useState } from 'preact/hooks'; +import styles from './checkbox.module.css'; + +function Checkbox({ id, name, value, checked: initialChecked, onChange, label }) { + const [isChecked, setIsChecked] = useState(initialChecked || false); + + const handleChange = (event) => { + setIsChecked(event.target.checked); + if (onChange) { + onChange(event); // Propagate the change event + } + }; + + return ( +
+ + +
+ ); +} + +export default Checkbox; \ No newline at end of file diff --git a/frontend/src/components/Buttons/checkbox.module.css b/frontend/src/components/Buttons/checkbox.module.css index e69de29..1043526 100644 --- a/frontend/src/components/Buttons/checkbox.module.css +++ b/frontend/src/components/Buttons/checkbox.module.css @@ -0,0 +1,51 @@ +/*TODO made by AI */ +.checkboxContainer { + display: flex; + align-items: center; + cursor: pointer; + user-select: none; /* Prevent text selection on click */ +} + +.nativeCheckbox { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.customCheckbox { + position: relative; + display: inline-block; + width: 20px; /* Adjust size as needed */ + height: 20px; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 3px; /* Optional rounded corners */ +} + +.customCheckbox:hover input ~ .checkmark { + background-color: #ddd; +} + +.nativeCheckbox:checked ~ .customCheckbox { + background-color: #2196F3; /* Active color */ + border: 1px solid #2196F3; +} + +.checkmark { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + color: white; /* Checkmark color */ + font-size: 16px; /* Adjust checkmark size */ +} + +.label { + margin-left: 8px; +} \ No newline at end of file diff --git a/frontend/src/components/Cards/row.jsx b/frontend/src/components/Cards/row.jsx index 7955952..dade80a 100644 --- a/frontend/src/components/Cards/row.jsx +++ b/frontend/src/components/Cards/row.jsx @@ -9,18 +9,19 @@ import Thumbnail from '../../assets/mod.svg' import DownloadIcon from '../../assets/download_alt.svg' function GridCard({item}) { + const item_page = "/mods/" + item.name; return (
- - - + + +

{item.description}

- {item.display_name} + {item.display_name} By {item.author}
- diff --git a/frontend/src/components/Cards/row.module.css b/frontend/src/components/Cards/row.module.css index a47ced0..4e0a7b0 100644 --- a/frontend/src/components/Cards/row.module.css +++ b/frontend/src/components/Cards/row.module.css @@ -18,6 +18,7 @@ height: 5rem; width: 5rem; + background-color: #2a2a2a; border: #3a3a3a .1rem solid; border-radius: .5rem; } @@ -33,6 +34,8 @@ font-size: 1.4rem; font-weight: 600; + + color: #ffffff; } .author { diff --git a/frontend/src/components/Fields/input_field.jsx b/frontend/src/components/Fields/input_field.jsx new file mode 100644 index 0000000..eb6512f --- /dev/null +++ b/frontend/src/components/Fields/input_field.jsx @@ -0,0 +1,36 @@ +// TODO made by AI + +import { h } from 'preact'; +import styles from './input_field.module.css'; // Optional: CSS Modules + +function InputField({ + id, + name, + label, + value, + onChange, + error, + placeholder, + required, + className, + ...rest // To accept other standard input props +}) { + return ( +
+ {label && } + + {error &&
{error}
} +
+ ); +} +export default InputField \ No newline at end of file diff --git a/frontend/src/components/Fields/input_field.module.css b/frontend/src/components/Fields/input_field.module.css new file mode 100644 index 0000000..4839223 --- /dev/null +++ b/frontend/src/components/Fields/input_field.module.css @@ -0,0 +1,43 @@ +.formGroup { + margin-bottom: 1rem; +} + +.label { + display: block; + margin-bottom: 0.5rem; +} + +.input { + + width: 100%; + padding: 0.75rem; + + font-size: 1rem; + font-family: 'IBM Plex Mono'; + color: #eaeaea; + + background-color: #2a2a2a; + border: transparent; + border-bottom: .2em solid #3a3a3a; + border-radius: .2rem; + box-sizing: border-box; /* (ensure padding doesn't affect width) */ + +} + +.input:focus { + outline: none; + + border-color: #353be5; + background-color: #2a2a2a; + color: #ffffff; +} + +.inputError { + border-color: #de3535; +} + +.errorMessage { + color: #de3535; + font-size: 0.875rem; + margin-top: 0.25rem; +} \ No newline at end of file diff --git a/frontend/src/styles/mods.css b/frontend/src/components/Fields/search.jsx similarity index 100% rename from frontend/src/styles/mods.css rename to frontend/src/components/Fields/search.jsx diff --git a/frontend/src/components/Fields/search.module.css b/frontend/src/components/Fields/search.module.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/pages/about.jsx b/frontend/src/pages/about.jsx index e69de29..5b8ca56 100644 --- a/frontend/src/pages/about.jsx +++ b/frontend/src/pages/about.jsx @@ -0,0 +1,36 @@ +// Functions +import { h } from 'preact'; + +// Images +import logo from '../assets/logo.png' +import dl_icon from '../assets/download.svg' + +// Styles +import '../styles/home.css' + +// Components +import Button from '../components/Buttons/button'; + + +function AboutPage() { + + return ( + <> + + + + +
About us
+
+
+
+ WF radio is a platform for all your personnal mods and modpacks. + The difference with already existing big platforms is that radio is self-hosted and open-source. + It's meant for your personnal works that you don't want to publish on the said platforms, but feel free to use it the way you want. + Don't hesitate to learn more about the project with the link below (not here yet). +
+ + ); +} + +export default AboutPage; \ No newline at end of file diff --git a/frontend/src/pages/home.jsx b/frontend/src/pages/home.jsx index 73db156..e776dd0 100644 --- a/frontend/src/pages/home.jsx +++ b/frontend/src/pages/home.jsx @@ -16,12 +16,12 @@ function HomePage() { return ( <> -
- - -
+ + + +
An open place for mods
- +
diff --git a/frontend/src/pages/mod_creation.jsx b/frontend/src/pages/mod_creation.jsx new file mode 100644 index 0000000..cd9bec5 --- /dev/null +++ b/frontend/src/pages/mod_creation.jsx @@ -0,0 +1,81 @@ +// Preact +import { h } from 'preact'; +import { useState, useEffect } from 'preact/hooks'; + +// Functions +import { createMod } from '../services/api'; + +// Components +import InputField from '../components/Fields/input_field' + +// Images +import logo from '../assets/logo.png' +import profile from '../assets/profile.svg' + +// Styles +import styles from '../styles/content_creation.module.css' +import Button from '../components/Buttons/button'; + +function ModCreationPage() { + + const user = "guill" //TODO + + const mod_infos = { + name: "" + } + + return ( + <> + + + + + +
+
+ + {/* Name */} + + + + {/* Display name */} + + + + +
+ + +
+ + + +

{user}

+
+ +
+
+ + ) +} + +export default ModCreationPage \ No newline at end of file diff --git a/frontend/src/pages/mod_page.jsx b/frontend/src/pages/mod_page.jsx new file mode 100644 index 0000000..66dfe79 --- /dev/null +++ b/frontend/src/pages/mod_page.jsx @@ -0,0 +1,120 @@ +// Preact +import { h } from 'preact'; +import { useState, useEffect } from 'preact/hooks'; + +// Functions +import { fetchMod } from '../services/api'; + +// Components + +// Images +import logo from '../assets/logo.png' +import profile from '../assets/profile.svg' +import download_icon from '../assets/download_alt.svg' +import favorite_icon from '../assets/favorite.svg' + +// Styles +import styles from '../styles/content_page.module.css' + +function ModPage({name}) { + + // UseState + const [mod, setMod] = useState({}) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + // UseEffect + useEffect(() => { + async function loadItems() { + setLoading(true); + setError(false); + try { + const fetched_mod = await fetchMod(name); + setMod(fetched_mod); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + } + + loadItems(); + }, []); // <-- Tells useEffect to run once after render + + if (loading) { + // TODO replace by loading screen + return
Loading mod
+ } + if (error) { + // TODO replace by popup + return
Couldn't load mod: {error}
+ } + + return ( + <> + + + + + +
+
+
+

{mod.display_name || mod.name }

+

{mod.mod_infos.full_description || "No description"}

+
+ + +
+ + + +

{mod.author}

+
+ +
+
+ + {mod.mod_infos.downloads_count || 0} +
+
+ + {mod.mod_infos.favorites_count || 0} +
+
+ +

Versions

+
+ + + + +
+ v3.0 +
+
+ + + + +
+ v2.0 +
+
+ + + + + +
+ v1.0 +
+
+
+ +
+
+ + ) +} + +export default ModPage \ No newline at end of file diff --git a/frontend/src/pages/modpacks.jsx b/frontend/src/pages/modpacks.jsx new file mode 100644 index 0000000..7e4e408 --- /dev/null +++ b/frontend/src/pages/modpacks.jsx @@ -0,0 +1,30 @@ +// Functions +import { h } from 'preact'; + +// Images +import logo from '../assets/logo.png' +import dl_icon from '../assets/download.svg' + +// Styles +import '../styles/home.css' + +// Components +import Button from '../components/Buttons/button'; + + +function ModpacksPage() { + + return ( + <> + + WF + modpacks + +
Coming soon™
+
+
+ + ); +} + +export default ModpacksPage; \ No newline at end of file diff --git a/frontend/src/pages/mods.jsx b/frontend/src/pages/mods.jsx index b081437..1cb3fdb 100644 --- a/frontend/src/pages/mods.jsx +++ b/frontend/src/pages/mods.jsx @@ -12,7 +12,6 @@ import FiltersPanel from '../components/Filters/panel' import logo from '../assets/logo.png' // Styles -import '../styles/mods.css' import GridCard from '../components/Cards/grid'; import RowCard from '../components/Cards/row'; @@ -51,16 +50,12 @@ function ModsPage() { return
Couldn't load mods: {error}
} - // Debugging - console.debug(mods); - const testArray = [(1, 'a'), (2, 'b'), (3, 'c')]; - return ( <> -
+ - -
+ +
{/* Test card */} @@ -71,11 +66,6 @@ function ModsPage() { // return
Test
return })} - - {testArray.map((item, index) => { - // - console.debug(index, item) - })}
); diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 4b3352b..a16886e 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -12,4 +12,52 @@ export async function fetchMods() { console.error('Failed to fetch items:', error); return []; } +} + +export async function fetchMod(mod_name) { + try { + const response = await fetch(`${API_BASE_URL}/mods/${mod_name}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error('Failed to fetch items:', error); + return null; + } +} + +export async function login(username, password) { + try { + const response = await fetch(`${API_BASE_URL}/login`, { + method: 'POST', + body: JSON.stringify({ username: username, password: password }) + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error('Failed to fetch items:', error); + return null; + } +} + +export async function createMod(username, password) { + try { + const response = await fetch(`${API_BASE_URL}/login`, { + method: 'POST', + body: JSON.stringify({ }) //TODO + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error('Failed to fetch items:', error); + return null; + } } \ No newline at end of file diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 3af2410..8584284 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -22,6 +22,10 @@ body { margin: 0; } +* { + transition: 200ms; +} + a { text-decoration: none; @@ -56,6 +60,33 @@ a { user-select: none; } +.logoSmall.img { + position: absolute; + top: 1.5rem; + left: .5rem; + height: 6em; + + padding: 1.5em; + + user-select: none; +} + +.logoSmall.text { + + position: absolute; + left: 8rem; + top: 4.1rem; + + margin: 0; + + color: #eaeaea; + font-family: Inter; + font-weight: 600; + font-size: 2.5em; + + user-select: none; +} + h1 { position: absolute; left: 0rem; @@ -94,16 +125,43 @@ h1 { position: absolute; top: 11rem; right: 4rem; - left: 4rem; + left: 22rem; bottom: 4rem; padding: 3rem; - background-color: #101010; + background-color: #1a1a1a; color: #eaeaea; border: #3a3a3a solid; border-width: .1em; border-radius: .5rem; +} + +/* Page transitions */ + +.page { + position: absolute; + top: 0; + left: 0; + width: 100%; + opacity: 0; + transition: opacity 0.2s ease-out; /* Adjust duration and easing */ +} + +.page-enter { + opacity: 0; +} + +.page-enter-active { + opacity: 1; +} + +.page-exit { + opacity: 1; +} + +.page-exit-active { + opacity: 0; } \ No newline at end of file diff --git a/frontend/src/styles/content_creation.module.css b/frontend/src/styles/content_creation.module.css new file mode 100644 index 0000000..b0732fd --- /dev/null +++ b/frontend/src/styles/content_creation.module.css @@ -0,0 +1,93 @@ +.container { + position: absolute; + top: 11rem; + right: 4rem; + left: 22rem; + bottom: 4rem; + display: inline; + --gap: 1rem; +} + +.form { + position: absolute; + top: 0; + bottom: 0; + + width: calc(70% - var(--gap)); + min-height: 20rem; + + padding: 3rem; + + background-color: #1a1a1a; + color: #eaeaea; + + border: #3a3a3a solid; + border-width: .1em; + border-radius: .5rem; + + overflow: scroll; +} + +.infosPanel { + position: absolute; + top: 0; + bottom: 0; + left: calc(70% + var(--gap) + 7rem); + right: 0; + + padding: 2rem; + min-height: 20rem; + align-items: center; + + background-color: #1a1a1a; + color: #eaeaea; + + border: #3a3a3a solid; + border-width: .1em; + border-radius: .5rem; + display: inline; + + overflow: scroll; +} + +.profilePicture { +/*TODO keep it center when overlapping*/ + margin-top: 1em; + margin-bottom: 1em; + display: block; + margin-left: auto; + margin-right: auto; + + width: 7em; +} + +.author { + margin: 0; + margin-bottom: 1rem; + + color: #c5c5c5; + font-weight: 600; + font-size: 2rem; + text-align: center; +} + +.panelTitle { + margin-top: 2rem; + + color: #eaeaea; + font-weight: 600; + font-size: 1.2em; +} + + +.smallField { + width: 20em; +} + +.createButton { + position: absolute; + bottom: 1em; + right: 1em; + + font-size: 1.4rem; +} \ No newline at end of file diff --git a/frontend/src/styles/content_page.module.css b/frontend/src/styles/content_page.module.css new file mode 100644 index 0000000..118af4b --- /dev/null +++ b/frontend/src/styles/content_page.module.css @@ -0,0 +1,197 @@ +.container { + position: absolute; + top: 11rem; + right: 4rem; + left: 22rem; + bottom: 4rem; + display: inline; + --gap: 1rem; +} + +.title { + + margin: 0; + margin-bottom: 1rem; + + color: #ffffff; + font-weight: 600; + font-size: 3rem; + + z-index: 2; +} + +.fullDescription { + + margin: 1rem; + margin-top: 3rem; + + color: #dadada; + font-size: 1.2rem; +} + +.content { + position: absolute; + top: 0; + bottom: 0; + + width: calc(70% - var(--gap)); + min-height: 20rem; + + padding: 3rem; + + background-color: #1a1a1a; + color: #eaeaea; + + border: #3a3a3a solid; + border-width: .1em; + border-radius: .5rem; + + overflow: scroll; +} + +.infosPanel { + position: absolute; + top: 0; + bottom: 0; + left: calc(70% + var(--gap) + 7rem); + right: 0; + + padding: 2rem; + min-height: 20rem; + align-items: center; + + background-color: #1a1a1a; + color: #eaeaea; + + border: #3a3a3a solid; + border-width: .1em; + border-radius: .5rem; + display: inline; + + overflow: scroll; +} + +.backgroundImage { + position: absolute; + top: 0; + right: 0%; + + height: 30%; + width: 70%; + + background: url("https://resources.oblic-parallels.fr/example.jpg"); + background-size: cover; + border-top-right-radius: .4rem; + -webkit-mask-image: radial-gradient(ellipse at top right, black 0%, #00000010 50%, transparent 70%); + + z-index: 0; +} + +.profilePicture { +/*TODO keep it center when overlapping*/ + margin-top: 1em; + margin-bottom: 1em; + display: block; + margin-left: auto; + margin-right: auto; + + width: 7em; +} + +.author { + margin: 0; + margin-bottom: 1rem; + + color: #c5c5c5; + font-weight: 600; + font-size: 2rem; + text-align: center; +} + +.panelTitle { + margin-top: 2rem; + + color: #eaeaea; + font-weight: 600; + font-size: 1.2em; +} + +.countsContainer { + margin-top: 2rem; + display: flex; + justify-content: center; +} + +.count { + + padding: .5em; + margin: 0 .5rem; + min-width: 3em; + + color: #cacaca; + background-color: #2a2a2a; + border: #3a3a3a .1rem solid; + border-radius: .5rem; +} + +.countIcon { + margin-bottom: -.4em; + margin-right: .5em; +} + +.timeline { + position: relative; +} + +.timeline::after { + position: absolute; + top: 0; + bottom: 0; + left: .2em; + + margin-left: -.2rem; + margin-top: 1em; + margin-bottom: 1em; + + width: .1rem; + + background-color: #3a3a3a; + + content: ''; +} + +.version { + margin-bottom: 2em; + margin-left: 1em; + + padding: .2em; + + color: #8a8a8a; + font-size: 1.3em; + border-radius: .5em; + + transition: 200ms; +} + +.versionDot { + position: absolute; + margin-left: -.66em; + margin-top: .7em; + + height: 1.5em; + width: 1.5em; + + z-index: 2; +} + +.versionDot > circle { + transition: 200ms; +} + +.versionDot > circle:hover { + fill: #eaeaea; +} + +.version:hover { + background-color: #3a3a3a; +} \ No newline at end of file diff --git a/frontend/src/styles/home.css b/frontend/src/styles/home.css index 80c9a0c..7b440cc 100644 --- a/frontend/src/styles/home.css +++ b/frontend/src/styles/home.css @@ -42,4 +42,16 @@ text-align: center; font-weight: 600; font-size: 2rem; +} + +.mainText { + position: absolute; + top: 48rem; + left: 50%; + transform: translate(-50%, -50%); /*Center (compensate size)*/ + + color: #eaeaea; + font-size: 1.5em; + text-align: justify; + } \ No newline at end of file