Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions src/components/StickyNav.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
import { getRelativeLocaleUrl } from "astro:i18n";
import { useTranslations } from "../i18n/utils";

interface Props {
id: string;
total: number;
prev: { slug: string; title: string } | null;
next: { slug: string; title: string } | null;
}

const { id, total, prev, next } = Astro.props;
const lang = (Astro.currentLocale as "es" | "en") || "es";
const t = useTranslations(lang);
const tipPath = lang === "es" ? "consejo" : "tip";
---

<div class="sticky bottom-6 left-0 right-0 z-40 pointer-events-none mt-12">
<div class="max-w-2xl mx-auto px-4">
<div class="bg-neutral-900/10 backdrop-blur-xl border border-white/10 rounded-full h-14 flex items-center justify-between px-2 shadow-2xl pointer-events-auto">
<!-- Bug Icon (GitHub) -->
<div class="flex items-center">
<a
href="https://github.com/midudev/100cosas.dev/issues/new"
target="_blank"
rel="noopener noreferrer"
class="p-2.5 text-neutral-500 hover:text-red-400 transition-colors rounded-full hover:bg-white/5"
title="Reportar un error / Report a bug"
>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m8 2 1.88 1.88"></path>
<path d="M14.12 3.88 16 2"></path>
<path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"></path>
<path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"></path>
<path d="M12 20v-9"></path>
<path d="M6.53 9C4.6 8.8 3 7.1 3 5"></path>
<path d="M17.47 9c1.93-.2 3.53-1.9 3.53-4"></path>
<path d="M4.8 19c1.1-1.1 2.2-2.2 3.1-4.6"></path>
<path d="M19.2 19c-1.1-1.1-2.2-2.2-3.1-4.6"></path>
</svg>
</a>
</div>

<!-- Center Nav -->
<div class="flex items-center gap-1">
{prev ? (
<a
href={getRelativeLocaleUrl(lang, `/${tipPath}/${prev.slug}`)}
class="p-2.5 text-neutral-400 hover:text-white transition-colors rounded-full hover:bg-white/5"
title={t("tip.prev")}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
</a>
) : (
<div class="p-2.5 text-neutral-800 cursor-not-allowed">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
</div>
)}

<div class="flex flex-col items-center px-2 min-w-[80px] sm:min-w-[100px]">
<span class="text-[9px] font-pixel text-neutral-500 uppercase tracking-widest leading-none mb-1">100cosasdev</span>
<span class="text-[11px] font-pixel text-sky-400/80 leading-none">#{id} / {total}</span>
</div>

{next ? (
<a
href={getRelativeLocaleUrl(lang, `/${tipPath}/${next.slug}`)}
class="p-2.5 text-neutral-400 hover:text-white transition-colors rounded-full hover:bg-white/5"
title={t("tip.next")}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
</a>
) : (
<div class="p-2.5 text-neutral-800 cursor-not-allowed">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
</div>
)}
</div>

<!-- Mark as Read -->
<div class="flex items-center">
<label class="cursor-pointer group/nav relative flex items-center pr-1">
<input
type="checkbox"
class="read-toggle sr-only"
data-id={id}
/>
<div class="flex items-center gap-2 px-4 py-2 rounded-full transition-all duration-300 border border-white/5 bg-white/5 group-hover/nav:bg-white/10 group-has-[:checked]/nav:bg-emerald-500 group-has-[:checked]/nav:hover:bg-emerald-400 group-has-[:checked]/nav:border-emerald-400/20 group-has-[:checked]/nav:flex-row-reverse">
<svg class="w-4 h-4 text-neutral-400 group-hover/nav:text-neutral-200 group-has-[:checked]/nav:text-emerald-50 transition-colors">
<use href="#icon-eye-check"></use>
</svg>
<span class="text-[11px] font-bold text-neutral-400 group-hover/nav:text-neutral-200 group-has-[:checked]/nav:text-emerald-50 transition-colors whitespace-nowrap">
<span class="block group-has-[:checked]/nav:hidden">{t("tip.mark_completed")}</span>
<span class="hidden group-has-[:checked]/nav:block">{t("tip.completed")}</span>
</span>
</div>
</label>
</div>
</div>
</div>
</div>
15 changes: 4 additions & 11 deletions src/components/TipPage.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getEntry, getCollection, render, type CollectionEntry } from "astro:con
import Layout from "../layouts/Layout.astro"
import Navbar from "./Navbar.astro"
import Footer from "./Footer.astro"
import StickyNav from "./StickyNav.astro"
import { getRelativeLocaleUrl } from "astro:i18n"
import { useTranslations } from "../i18n/utils"

Expand All @@ -21,7 +22,7 @@ interface Props {
next?: TipNavItem | null
}

const { entry, total, alternateUrl, canonical, noindex = false, prev, next } = Astro.props
const { entry, total, alternateUrl, canonical, noindex = false, prev = null, next = null } = Astro.props
const { Content } = await render(entry)
const { id, title, category, author: authorId, categoryColor } = entry.data
const description = (entry.body ?? "").slice(0, 160).replace(/[#>]/g, "").trim()
Expand Down Expand Up @@ -358,17 +359,9 @@ const jsonLd = {
</nav>
</aside>
</div>

<StickyNav id={id} total={total} prev={prev} next={next} />
</main>

<Footer />
</Layout>

<script define:vars={{ id }}>
const STORAGE_KEY = "read-tips"
const stored = localStorage.getItem(STORAGE_KEY)
const readTips = stored ? JSON.parse(stored) : []
if (!readTips.includes(id)) {
readTips.push(id)
localStorage.setItem(STORAGE_KEY, JSON.stringify(readTips))
}
</script>
4 changes: 4 additions & 0 deletions src/i18n/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export const ui = {
'tip.more_by_author': 'Más de este autor',
'tip.view_profile': 'Ver perfil',
'tip.category_label': 'Categoría',
'tip.mark_completed': '¿Completar?',
'tip.completed': 'Completado',
'author.title': 'Autor',
'author.tips': 'Consejos de',
'book.download': 'Descargar gratis',
Expand Down Expand Up @@ -122,6 +124,8 @@ export const ui = {
'tip.more_by_author': 'More by this author',
'tip.view_profile': 'View profile',
'tip.category_label': 'Category',
'tip.mark_completed': 'Complete?',
'tip.completed': 'Completed',
'author.title': 'Author',
'author.tips': 'Tips by',
'book.download': 'Download free',
Expand Down