diff --git a/public/icons/keyboard_double_arrow_up_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg b/public/icons/keyboard_double_arrow_up_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg new file mode 100644 index 0000000..ea09945 --- /dev/null +++ b/public/icons/keyboard_double_arrow_up_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/style.css b/public/style.css index f255781..98548d4 100644 --- a/public/style.css +++ b/public/style.css @@ -21,7 +21,6 @@ --color-blue-600: oklch(54.6% 0.245 262.881); --color-blue-700: oklch(48.8% 0.243 264.376); --color-gray-50: oklch(98.5% 0.002 247.839); - --color-gray-100: oklch(96.7% 0.003 264.542); --color-gray-200: oklch(92.8% 0.006 264.531); --color-gray-300: oklch(87.2% 0.01 258.338); --color-gray-400: oklch(70.7% 0.022 261.325); @@ -205,6 +204,12 @@ } } @layer utilities { + .pointer-events-none { + pointer-events: none; + } + .visible { + visibility: visible; + } .relative { position: relative; } @@ -301,6 +306,14 @@ .flex-1 { flex: 1; } + .translate-y-0 { + --tw-translate-y: calc(var(--spacing) * 0); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-y-2 { + --tw-translate-y: calc(var(--spacing) * 2); + translate: var(--tw-translate-x) var(--tw-translate-y); + } .cursor-pointer { cursor: pointer; } @@ -368,9 +381,6 @@ .bg-gray-50 { background-color: var(--color-gray-50); } - .bg-gray-100 { - background-color: var(--color-gray-100); - } .bg-gray-900 { background-color: var(--color-gray-900); } @@ -511,6 +521,12 @@ .underline-offset-\[0\.3rem\] { text-underline-offset: 0.3rem; } + .opacity-0 { + opacity: 0%; + } + .opacity-100 { + opacity: 100%; + } .shadow { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -576,13 +592,6 @@ } } } - .hover\:bg-gray-200 { - &:hover { - @media (hover: hover) { - background-color: var(--color-gray-200); - } - } - } .hover\:bg-red-700 { &:hover { @media (hover: hover) { @@ -665,11 +674,6 @@ background-color: #2e2e33; } } - .dark\:bg-\[\#333\] { - &:where(.dark, .dark *) { - background-color: #333; - } - } .dark\:bg-\[\#dadadb\] { &:where(.dark, .dark *) { background-color: #dadadb; @@ -759,15 +763,6 @@ } } } - .dark\:hover\:bg-\[\#444\] { - &:where(.dark, .dark *) { - &:hover { - @media (hover: hover) { - background-color: #444; - } - } - } - } .dark\:hover\:text-\[\#dadadb\] { &:where(.dark, .dark *) { &:hover { @@ -787,6 +782,21 @@ } } } +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0; +} @property --tw-space-y-reverse { syntax: "*"; inherits: false; @@ -910,24 +920,12 @@ syntax: "*"; inherits: false; } -@property --tw-translate-x { - syntax: "*"; - inherits: false; - initial-value: 0; -} -@property --tw-translate-y { - syntax: "*"; - inherits: false; - initial-value: 0; -} -@property --tw-translate-z { - syntax: "*"; - inherits: false; - initial-value: 0; -} @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-translate-z: 0; --tw-space-y-reverse: 0; --tw-border-style: solid; --tw-leading: initial; @@ -956,9 +954,6 @@ --tw-backdrop-saturate: initial; --tw-backdrop-sepia: initial; --tw-duration: initial; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-translate-z: 0; } } } diff --git a/src/pages/home.rs b/src/pages/home.rs index b744df7..fbeb197 100644 --- a/src/pages/home.rs +++ b/src/pages/home.rs @@ -172,14 +172,69 @@ fn Pagination() -> Element { #[component] fn Footer() -> Element { + let mut visible = use_signal(|| false); + + use_effect(move || { + #[cfg(target_arch = "wasm32")] + { + if let Some(window) = web_sys::window() { + let closure = wasm_bindgen::prelude::Closure::wrap(Box::new(move || { + if let Some(w) = web_sys::window() { + let threshold = w.inner_height().ok() + .and_then(|h| h.as_f64()) + .unwrap_or(0.0); + let scroll_y = w.scroll_y().unwrap_or(0.0); + let new_visible = scroll_y > threshold; + visible.set(new_visible); + } + }) as Box); + + let _ = window.add_event_listener_with_callback("scroll", wasm_bindgen::JsCast::unchecked_ref(closure.as_ref())); + + let threshold = window.inner_height().ok() + .and_then(|h| h.as_f64()) + .unwrap_or(0.0); + let scroll_y = window.scroll_y().unwrap_or(0.0); + visible.set(scroll_y > threshold); + + closure.forget(); + } + } + }); + + let link_class = use_memo(move || { + let base = "p-2 rounded-full cursor-pointer hover:opacity-80 transition-all duration-300 text-gray-600 dark:text-gray-300"; + if visible() { + format!("{} opacity-100 translate-y-0", base) + } else { + format!("{} opacity-0 translate-y-2 pointer-events-none", base) + } + }); + rsx! { footer { class: "w-full border-t border-gray-200 dark:border-[#333] mt-auto", div { class: "max-w-3xl mx-auto px-6 py-5 flex items-center justify-between text-sm text-gray-400 dark:text-[#9b9c9d]", span { "© 2026 Yggdrasil Blog" } - button { - class: "p-2 rounded-full bg-gray-100 dark:bg-[#333] hover:bg-gray-200 dark:hover:bg-[#444] transition-colors", - onclick: move |_| scroll_to_top(), - "↑" + a { + class: "{link_class}", + href: "#top", + aria_label: "go to top", + title: "Go to Top (Alt + G)", + accesskey: "g", + onclick: move |evt| { + evt.prevent_default(); + scroll_to_top(); + }, + svg { + xmlns: "http://www.w3.org/2000/svg", + height: "24px", + view_box: "0 -960 960 960", + width: "24px", + fill: "currentColor", + path { + d: "m296-224-56-56 240-240 240 240-56 56-184-183-184 183Zm0-240-56-56 240-240 240 240-56 56-184-183-184 183Z", + } + } } } } @@ -194,10 +249,10 @@ fn scroll_to_top() { options.top(0.0); options.behavior(web_sys::ScrollBehavior::Smooth); let _ = window.scroll_to_with_scroll_to_options(&options); + + if let Ok(history) = window.history() { + let _ = history.replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(" ")); + } } } - #[cfg(not(target_arch = "wasm32"))] - { - let _ = (); - } }