mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-20 11:04:38 +00:00
feat(web): add footer
This commit is contained in:
parent
a93ff3f920
commit
898d5f7195
@ -1,11 +1,10 @@
|
||||
use crate::pages::{aes::AesPage, des::DesPage, home::Home};
|
||||
use crate::pages::{aes::AesPage, des::DesPage, footer::Footer, header::Header, home::Home};
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
|
||||
use leptos_router::{
|
||||
StaticSegment,
|
||||
components::{A, Route, Router, Routes},
|
||||
components::{Route, Router, Routes},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[must_use]
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
@ -26,60 +25,15 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Theme {
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
const fn inverse(self) -> Self {
|
||||
match self {
|
||||
Self::Light => Self::Dark,
|
||||
Self::Dark => Self::Light,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Theme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
Self::Light => "☀️ Light",
|
||||
Self::Dark => "🌙 Dark",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
pub fn App() -> impl IntoView {
|
||||
provide_meta_context();
|
||||
|
||||
let (theme, set_theme) = signal(Theme::Dark);
|
||||
|
||||
let toggle_theme = move |_| {
|
||||
set_theme.update(|t| *t = t.inverse());
|
||||
|
||||
if let Some(body) = document().body() {
|
||||
let class_list = body.class_list();
|
||||
match theme.get() {
|
||||
Theme::Light => {
|
||||
let _ = class_list.remove_1("dark-theme");
|
||||
let _ = class_list.add_1("light-theme");
|
||||
}
|
||||
Theme::Dark => {
|
||||
let _ = class_list.remove_1("light-theme");
|
||||
let _ = class_list.add_1("dark-theme");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
// injects a stylesheet into the document <head>
|
||||
// id=leptos means cargo-leptos will hot-reload this stylesheet
|
||||
<Stylesheet id="leptos" href="/pkg/web2.css" />
|
||||
<Stylesheet id="leptos" href="/pkg/web.css" />
|
||||
|
||||
// sets the document title
|
||||
<Title text="Cipher Workshop" />
|
||||
@ -87,22 +41,7 @@ pub fn App() -> impl IntoView {
|
||||
// content for this welcome page
|
||||
<Router>
|
||||
<div class="app-containter">
|
||||
<nav class="main-nav">
|
||||
<ul>
|
||||
<li>
|
||||
<A href="/">"Home"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/des">"DES"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/aes">"AES"</A>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="theme-toggle" on:click=toggle_theme>
|
||||
{move || theme.get().to_string()}
|
||||
</button>
|
||||
</nav>
|
||||
<Header />
|
||||
<main>
|
||||
<Routes fallback=|| "Page not found.".into_view()>
|
||||
<Route path=StaticSegment("/") view=Home />
|
||||
@ -110,6 +49,7 @@ pub fn App() -> impl IntoView {
|
||||
<Route path=StaticSegment("/aes") view=AesPage />
|
||||
</Routes>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</Router>
|
||||
}
|
||||
|
||||
23
web/src/pages/footer.rs
Normal file
23
web/src/pages/footer.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::components::A;
|
||||
|
||||
#[component]
|
||||
pub fn Footer() -> impl IntoView {
|
||||
view! {
|
||||
<footer class="app-footer">
|
||||
<div class="footer-content">
|
||||
<p>
|
||||
"🔒 " <strong>"Client-Side Security:"</strong>
|
||||
" All encryption and decryption operations happen entirely in your browser. "
|
||||
"No data is ever sent to a server. "
|
||||
"You can verify this by disconnecting your internet."
|
||||
</p>
|
||||
<div class="footer-links">
|
||||
<A href="https://github.com/kristoferssolo/cipher-workshop" target="_blank">
|
||||
"View Source on GitHub"
|
||||
</A>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
}
|
||||
}
|
||||
70
web/src/pages/header.rs
Normal file
70
web/src/pages/header.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::components::A;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[component]
|
||||
pub fn Header() -> impl IntoView {
|
||||
let (theme, set_theme) = signal(Theme::Dark);
|
||||
|
||||
let toggle_theme = move |_| {
|
||||
set_theme.update(|t| *t = t.inverse());
|
||||
|
||||
if let Some(body) = document().body() {
|
||||
let class_list = body.class_list();
|
||||
match theme.get() {
|
||||
Theme::Light => {
|
||||
let _ = class_list.remove_1("dark-theme");
|
||||
let _ = class_list.add_1("light-theme");
|
||||
}
|
||||
Theme::Dark => {
|
||||
let _ = class_list.remove_1("light-theme");
|
||||
let _ = class_list.add_1("dark-theme");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
<nav class="main-nav">
|
||||
<ul>
|
||||
<li>
|
||||
<A href="/">"Home"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/des">"DES"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/aes">"AES"</A>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="theme-toggle" on:click=toggle_theme>
|
||||
{move || theme.get().to_string()}
|
||||
</button>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Theme {
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
const fn inverse(self) -> Self {
|
||||
match self {
|
||||
Self::Light => Self::Dark,
|
||||
Self::Dark => Self::Light,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Theme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
Self::Light => "☀️ Light",
|
||||
Self::Dark => "🌙 Dark",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
pub mod aes;
|
||||
pub mod des;
|
||||
pub mod footer;
|
||||
pub mod header;
|
||||
pub mod home;
|
||||
|
||||
@ -27,6 +27,7 @@ $l-hl-low: #f4ede8;
|
||||
$l-hl-high: #cecacd;
|
||||
|
||||
$control-height: 46px;
|
||||
$trans-speed: 0.3s ease;
|
||||
|
||||
:root,
|
||||
body.dark-theme {
|
||||
@ -66,20 +67,29 @@ body.light-theme {
|
||||
--focus-ring: #{rgba($l-rose, 0.3)};
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Inter", "Segoe UI", sans-serif;
|
||||
background-color: var(--bg-body);
|
||||
color: var(--text-main);
|
||||
margin: 0;
|
||||
transition:
|
||||
background-color 0.3s,
|
||||
color 0.3s;
|
||||
background-color $trans-speed,
|
||||
color $trans-speed;
|
||||
}
|
||||
|
||||
.app-containter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
@ -89,6 +99,9 @@ body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition:
|
||||
background-color $trans-speed,
|
||||
border-color $trans-speed;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
@ -111,6 +124,13 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
@ -135,11 +155,13 @@ body {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
transition:
|
||||
background-color $trans-speed,
|
||||
border-color $trans-speed,
|
||||
color $trans-speed;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
|
||||
p {
|
||||
@ -167,16 +189,16 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.cipher-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
transition:
|
||||
background-color $trans-speed,
|
||||
border-color $trans-speed,
|
||||
box-shadow $trans-speed;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@ -400,6 +422,19 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group textarea,
|
||||
.format-controls,
|
||||
.radio-group,
|
||||
.result-box,
|
||||
.result-toolbar,
|
||||
.format-controls select {
|
||||
transition:
|
||||
background-color $trans-speed,
|
||||
border-color $trans-speed,
|
||||
color $trans-speed;
|
||||
}
|
||||
|
||||
.btn-copy {
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user