Netnyahoo
Mac용 실질적인 Chromium으로, Arc 사이드바와 Dia 폴리시를 갖추고 계정 없이 무료·오픈소스이며 광고·추적·소송에 면역이다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Web personal de Shande Villa, fotógrafo de calle que vive en Guangzhou. Sirve también como enlace de la bio de Instagram.
HTML y CSS estáticos, sin frameworks, sin dependencias en el navegador y sin cookies. El único script de terceros es el captcha de Cloudflare (Turnstile), y solo se carga cuando alguien abre el formulario de contacto. Pensada para Cloudflare Pages; el formulario usa una Pages Function (functions/).
site/ ← lo único que se despliega (directorio de salida)
index.html texto en español (funciona sin JavaScript y es lo que ve Google)
css/estilo.css todo el diseño
js/ idioma, menú, formulario, filtros del portfolio, visor de fotos (sin dependencias)
i18n/zh.json, en.json textos en chino e inglés
img/ fotos optimizadas (WebP, varios tamaños)
fonts/ fuentes autoalojadas (+ sus licencias)
og.jpg, favicon.ico, icon-192.png, icon-512.png, apple-touch-icon.png (el logo 山德)
sitemap.xml, robots.txt, _redirects, _headers
originales/ fotos originales, FUERA del despliegue
tools/ herramientas para quien edita (no se despliegan)
functions/api/contacto.js backend del formulario de contacto (Pages Function)
build.mjs único script de build: RSS de Substack + fuente china
REVISAR.md traducciones al chino y al inglés pendientes de revisar
node build.mjs # (opcional) trae tu último post de Substack
python3 -m http.server -d site 8000
# abre http://localhost:8000
Hace falta Node 18 o superior. Para la fuente china, build.mjs usa Python 3 y la librería fonttools (se instala sola en el build; si no se puede, se queda la fuente que ya está en el repositorio).
main (o rediseno mientras la pruebas).node build.mjssiteNODE_VERSION = 22.*.pages.dev para probar.shandevilla.com (y www.shandevilla.com si lo quieres).kungfundidos.com o www.kungfundidos.comconcat("https://shandevilla.com", http.request.uri.path), estado 301, conservando la cadena de consulta.A a 192.0.2.1).Las URLs antiguas del blog (/blog, /es/blog/…, /en/blog/…, /zh-cn/blog/…) y las páginas sueltas (/obra, /sobre-mi, /contacto) llevan a la portada con un 301 gracias a site/_redirects.
El título y el enlace de tu último post (la fila «Substack» del bloque «Fuera de China») se leen del RSS al construir la web, no desde el navegador. Para que se actualicen sin tocar nada:
main).POST cada día, por ejemplo con un trabajo programado gratuito en cron-job.org o con un Worker con Cron Trigger de Cloudflare.Si Substack no responde el día del build, la web se publica igual: la fila de Substack sale sin título y enlaza a tu perfil.
El botón «Abrir el formulario» de la sección Contacto abre un popup con email, asunto y descripción. Tu correo no aparece en ningún sitio: solo vive en una variable secreta de Cloudflare. Defensas contra spam: captcha Turnstile verificado en el servidor, campo trampa oculto para robots, comprobación de origen, límites de tamaño y limpieza de caracteres de control.
Hasta que lo actives, el popup avisa de que «todavía no está activado» y ofrece Instagram como alternativa. Para activarlo:
shandevilla.com, kungfundidos.com y *.pages.dev (o el de tu proyecto). Modo Managed. Te da una Site key (pública) y una Secret key.shandevilla.com → Email → Email Routing → activar (Cloudflare añade los registros DNS). En Destination addresses añade tu Gmail y pulsa el enlace del correo de verificación; hasta entonces no envía nada.wrangler.jsonc + worker.js, ya incluidos). Crea el Worker: Workers & Pages → Create → Import a repository (este repo, rama main) con Build command node build.mjs, Deploy command npx wrangler deploy y Variable de build TURNSTILE_SITE_KEY = la Site key. Luego en el Worker, Settings → Variables and Secrets:| Nombre | Tipo | Valor |
|---|---|---|
TURNSTILE_SECRET | Secret | la Secret key |
CONTACT_TO | Secret | tu correo (ya verificado en Email Routing) |
CONTACT_FROM (el remitente, una dirección de tu dominio) y el binding EMAIL ya vienen en wrangler.jsonc; no los pongas en el panel. Después, mueve los dominios shandevilla.com y kungfundidos.com al Worker (Settings → Domains & Routes) y quita el proyecto de Pages.
Alternativa sin Email Routing: RESEND_API_KEY (Secret); la función usa Resend solo cuando no hay binding EMAIL.
CONTACT_TO nunca debe ir al repositorio. Para probarlo en local hace falta wrangler pages dev site.
En site/index.html, justo antes del final, hay el snippet comentado. Crea el sitio en Analytics & Logs → Web Analytics, pon tu token, descomenta la línea y amplía la política CSP de site/_headers (las dos líneas que hay que cambiar están anotadas al principio de ese archivo).
La página /portfolio/ (y /en/portfolio/, /zh/portfolio/) enseña todas las fotos de las carpetas de serie de originales/ (calle, noche, retratos-urbanos, hongkong, macao, arquitectura, detalles, retratos) en un mosaico, con filtros por serie (la carpeta) y por objetivo (se lee del EXIF de cada original; la Sony ZV-1 cuenta como objetivo y las cámaras no se filtran), y el visor al pulsar una foto. No se mira capturas, personales, reviews ni varios.
originales/ y ejecuta python3 tools/optimizar_portfolio.py (necesita ImageMagick y Pillow). Crea los WebP (sin EXIF) en site/img/portfolio/ y tools/portfolio-fotos.json. Hacen falta ese commit y el push; build.mjs genera la página en cada despliegue."rollos" en tools/seleccion.json; dentro de cada serie van primero las fotos de esa lista y después el resto por nombre.tools/objetivos.json traduce el nombre que escribe la cámara (p. ej. AF 23/1.8 XF) al nombre bonito. Si sale un objetivo con un nombre raro, añade una línea ahí y vuelve a ejecutar el script. Las fotos sin dato de objetivo solo salen en «Todos»./portfolio/#noche. Los textos de la página están en tools/portfolio.mjs (objeto PF).Cada review son tres textos en Markdown (uno por idioma) en reviews/<nombre>/:
reviews/ttartisan-23mm/es.md en.md zh.md el texto (si falta un idioma, esa review no sale en ese idioma)
originales/reviews/ttartisan-23mm/ fotos originales (fuera del despliegue)
Cabecera de cada .md (igual en los tres):
---
title: 'Review a fondo: TTArtisan AF 23mm F1.8'
description: 'Una frase que aparece en el listado y en Google.'
pubDate: 2025-01-13
marca: 'TTArtisan'
producto: 'TTArtisan AF 23mm F1.8'
cover: "./cover.jpg"
tags: ["Review", "TTArtisan", "Fujifilm"]
---
producto es el nombre del objetivo y marca la marca (si falta, se usa la primera palabra del nombre). El listado /reviews/ enseña las 4 últimas con foto y, debajo, un catálogo de todas ordenado por marca y nombre con un buscador (aparece cuando hay más de 4).
Después va el texto con ## (secciones), ###, negrita, listas con *, tablas (la ficha técnica es una tabla | Característica | Especificación |), --- y fotos con ; si la línea siguiente es *texto en cursiva*, es el pie de foto. Si la primera foto es la misma que cover, no se repite.
Para publicar una review nueva:
reviews/<nombre>/es.md (y en.md, zh.md si la quieres en esos idiomas) y deja las fotos en originales/reviews/<nombre>/.python3 tools/optimizar_reviews.py (crea los WebP de 800 y 1600 px en site/img/reviews/ y imagenes.json; necesita ImageMagick).reviews/ y site/img/reviews/). build.mjs genera las páginas en cada despliegue: /reviews/, /en/reviews/, /zh/reviews/ y una página por review, con hreflang y sitemap. Las páginas generadas no se guardan en git.site/index.html (los textos con data-i18n="…").site/i18n/zh.json y site/i18n/en.json con la misma clave. Todas las traducciones actuales son mías y están marcadas en REVISAR.md.data-i18n, añade su clave en los dos JSON.zh.json y en index.html (lo hace build.mjs). Si un carácter chino sale con otra tipografía, falta volver a construir.Las redes están en dos «carretes» de index.html: Fuera de China (franja amarilla con pestaña roja) y En China (franja verde, filas más bajas en dos columnas). Cada enlace es un <li>; la numeración (1A, 2A…) sale sola, así que si añades o quitas uno, el resto se renumera. Para YouTube, cuando tengas canal, copia una fila del grupo «Fuera de China» y pon su URL.
originales/<serie>/.tools/seleccion.json: "foto_grande" es la foto a sangre del principio (con "recorte" opcional, "AnchoxAlto+X+Y", para quitar bordes: el original no se toca) y "polaroid" la polaroid con tu retrato. "rollos" son las series del portfolio (ver «Portfolio»).python3 tools/optimizar_fotos.py (necesita ImageMagick con WebP). Crea las versiones WebP de la portada y la polaroid y reescribe ese trozo de index.html. No toca site/img/portfolio ni site/img/reviews.Los originales nunca se despliegan: solo se publica site/.
Se genera con tools/generar_og.py (necesita las TTF de Source Serif 4 y DM Mono; las que hay en site/fonts están recortadas). Hay que repetirlo solo si cambia la portada.
| Uso | Fuente | Dónde |
|---|---|---|
| Títulos y nombres (cursiva condensada) | Instrument Serif | site/fonts/, recortada a latín (31 KB) |
| Texto y menús | Hanken Grotesk, peso variable 300–600 | site/fonts/, recortada a latín (28 KB) |
| Texto impreso en el borde de la película y números de fotograma | DM Mono | site/fonts/ (9 KB) |
| Firma 山德 de la polaroid | Long Cang, recortada a esos dos caracteres | site/fonts/ (1,3 KB; tools/subset_firma.py para cambiarla) |
| Títulos en chino | Noto Serif SC, peso variable 400–600 | se recorta en cada build desde tools/fuentes-origen/ |
| Texto corrido en chino | Sans del sistema (PingFang SC, Microsoft YaHei, Noto Sans CJK) | no se descarga |
Reparto fijo (para que no se mezclen): serif cursiva solo en el título principal; serif redonda en los nombres y títulos de las listas; sans en el texto, el menú, los títulos de sección y todas las etiquetas («Fuera de China», «Último post», «Xiaohongshu»); mono solo en los números de fotograma; la letra a mano, solo en la firma de la polaroid.
La fuente china solo se descarga cuando el visitante elige chino. Todas las que se descargan son SIL Open Font License; las licencias están junto a los archivos. Los archivos de site/fonts son recortes técnicos (solo los caracteres necesarios) de las fuentes originales. python3 tools/subset_latin.py regenera las latinas desde tools/fuentes-origen/.
build.mjs añade ?v=<huella> a css/estilo.css, js/*.js y i18n/*.json, así que cada despliegue obliga al navegador a bajar la versión nueva. Sin esto, un móvil podía mezclar un HTML nuevo con un CSS viejo guardado en caché. Las fuentes latinas no cambian y se cachean un año; la fuente china se regenera en cada build y se cachea una hora; las fotos, un día.
#F1ECE2 sobre el fondo #120D0A y sobre las ventanas oscuras de la tira, más de 15:1; texto secundario #C4B8A8 10:1; ámbar #F5A03A 9:1. Pasa AA con margen y axe-core no da avisos.prefers-reduced-motion no se mueve nada.<dialog> modal: foco atrapado, Esc cierra, mensajes de estado con role=status.lang que cambia con el idioma.prefers-reduced-motion: sin el avance de la tira al cargar.width y height, srcset y carga diferida (salvo la portada).