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 _ = ();
- }
}