diff --git a/landing/app.js b/landing/app.js index d422ed9..f8d60c3 100644 --- a/landing/app.js +++ b/landing/app.js @@ -1,8 +1,9 @@ -// NextSync landing - tema, idioma, slider con lightbox, scrollspy y reveals. +// NextSync landing - tema, idioma, menu movil, slider con lightbox y escena de sync. (function () { "use strict"; var root = document.documentElement; + var dict = function () { return I18N[lang] || I18N.es; }; // ---------- Tema ---------- function systemTheme() { @@ -24,60 +25,82 @@ }); // ---------- Idioma ---------- - var langSelect = document.getElementById("langSelect"); + var langButtons = [].slice.call(document.querySelectorAll(".language-switch button")); var lang = currentLang(); - langSelect.value = lang; applyLang(lang); - langSelect.addEventListener("change", function () { - lang = langSelect.value; - applyLang(lang); + syncLangButtons(); + langButtons.forEach(function (btn) { + btn.addEventListener("click", function () { + lang = btn.getAttribute("data-lang"); + applyLang(lang); + syncLangButtons(); + }); + }); + function syncLangButtons() { + langButtons.forEach(function (btn) { + var on = btn.getAttribute("data-lang") === lang; + btn.classList.toggle("active", on); + btn.setAttribute("aria-pressed", on ? "true" : "false"); + }); + } + + // ---------- Menu movil ---------- + var mobileMenu = document.getElementById("mobileMenu"); + mobileMenu.querySelectorAll("a").forEach(function (a) { + a.addEventListener("click", function () { mobileMenu.open = false; }); + }); + document.addEventListener("keydown", function (e) { + if (e.key === "Escape" && mobileMenu.open) mobileMenu.open = false; }); // ---------- Slider de capturas ---------- - // Dos diapositivas por idioma: tema claro y oscuro. - function shot(base, cap, theme) { - return { - src: "assets/shots/" + base + "-960.webp", - srcset: "assets/shots/" + base + "-320.webp 320w, " + - "assets/shots/" + base + "-640.webp 640w, " + - "assets/shots/" + base + "-960.webp 960w, " + - "assets/shots/" + base + "-1080.webp 1080w, " + - "assets/shots/" + base + ".webp 1800w", - full: "assets/shots/" + base + ".webp", - thumb: "assets/shots/" + base + "-320.webp", - cap: cap, - theme: theme - }; - } - var slides = { - es: [shot("main-es-light", "shots.capLight", "light"), shot("main-es-dark", "shots.capDark", "dark")], - en: [shot("main-en-light", "shots.capLight", "light"), shot("main-en-dark", "shots.capDark", "dark")] - }; - var slideImg = document.getElementById("slideImg"); - var slideCaption = document.getElementById("slideCaption"); - var thumbs = document.getElementById("slideThumbs"); + // Three windows times two themes, in the current language. + var WINDOWS = [ + { base: "main", cap: "shots.capMain", alt: "shots.altMain" }, + { base: "activity", cap: "shots.capActivity", alt: "shots.altActivity" }, + { base: "settings", cap: "shots.capSettings", alt: "shots.altSettings" } + ]; + var shotImg = document.getElementById("slideImg"); + var shotCaption = document.getElementById("shotCaption"); + var shotViewport = document.getElementById("shotViewport"); var lightbox = document.getElementById("lightbox"); var lightboxImg = document.getElementById("lightboxImg"); - var slideIndex = 0; + var lightboxCaption = document.getElementById("lightboxCaption"); + var windowButtons = [].slice.call(document.querySelectorAll("[data-window]")); + var themeButtons = [].slice.call(document.querySelectorAll("[data-shot-theme]")); + var winIndex = 0; + var shotTheme = currentTheme(); - function activeSlides() { return slides[lang] || slides.es; } + function path(win, theme, suffix) { + return "assets/shots/" + WINDOWS[win].base + "-" + lang + "-" + theme + suffix; + } + function captionKey(win, theme) { + return dict()[WINDOWS[win].cap + (theme === "dark" ? "Dark" : "Light")] || ""; + } + function altKey(win) { + return dict()[WINDOWS[win].alt] || ""; + } - function renderThumbs() { - thumbs.innerHTML = ""; - var dict = I18N[lang] || I18N.es; - activeSlides().forEach(function (s, i) { - var b = document.createElement("button"); - b.setAttribute("role", "tab"); - b.setAttribute("aria-selected", i === slideIndex ? "true" : "false"); - b.setAttribute("aria-controls", "slideViewport"); - b.setAttribute("aria-label", dict[s.cap] || ""); - b.tabIndex = i === slideIndex ? 0 : -1; - var img = document.createElement("img"); - img.src = s.thumb; img.alt = ""; img.loading = "lazy"; img.decoding = "async"; - img.width = 96; img.height = 64; - b.appendChild(img); - b.addEventListener("click", function () { showSlide(i); }); - thumbs.appendChild(b); + function paintSlide() { + shotImg.srcset = [ + path(winIndex, shotTheme, "-320.webp") + " 320w", + path(winIndex, shotTheme, "-640.webp") + " 640w", + path(winIndex, shotTheme, "-960.webp") + " 960w", + path(winIndex, shotTheme, "-1080.webp") + " 1080w", + path(winIndex, shotTheme, ".webp") + " 1440w" + ].join(", "); + shotImg.src = path(winIndex, shotTheme, "-1080.webp"); + shotImg.alt = altKey(winIndex); + shotCaption.textContent = captionKey(winIndex, shotTheme); + windowButtons.forEach(function (b, i) { + var on = i === winIndex; + b.classList.toggle("active", on); + b.setAttribute("aria-selected", on ? "true" : "false"); + }); + themeButtons.forEach(function (b) { + var on = b.getAttribute("data-shot-theme") === shotTheme; + b.classList.toggle("active", on); + b.setAttribute("aria-selected", on ? "true" : "false"); }); } @@ -86,84 +109,85 @@ function mod(i, n) { return ((i % n) + n) % n; } - function setSlide(i) { - var list = activeSlides(); - slideIndex = mod(i, list.length); - var s = list[slideIndex]; - slideImg.src = s.src; - slideImg.srcset = s.srcset; - slideCaption.textContent = (I18N[lang] || I18N.es)[s.cap] || ""; - [...thumbs.children].forEach(function (b, j) { - b.setAttribute("aria-selected", j === slideIndex ? "true" : "false"); - b.tabIndex = j === slideIndex ? 0 : -1; - }); - } - - // dir: +1 next, -1 previous. Without it the direction comes from the index. - function showSlide(i, dir) { - var list = activeSlides(); - var target = mod(i, list.length); - if (dir === undefined) dir = target === slideIndex ? 1 : (target > slideIndex ? 1 : -1); - if (target === slideIndex || animating || reduceMotion.matches) { - setSlide(target); - slideImg.style.transition = ""; - slideImg.style.transform = ""; - slideImg.style.opacity = ""; + // dir: +1 next, -1 previous. + function showWindow(i, dir) { + var target = mod(i, WINDOWS.length); + if (dir === undefined) dir = target === winIndex ? 1 : (target > winIndex ? 1 : -1); + if (target === winIndex || animating || reduceMotion.matches) { + winIndex = target; + paintSlide(); + shotImg.style.transition = ""; + shotImg.style.transform = ""; + shotImg.style.opacity = ""; return; } animating = true; - slideImg.style.transition = "transform 180ms ease, opacity 180ms ease"; - slideImg.style.transform = "translateX(" + (-dir * 32) + "px)"; - slideImg.style.opacity = "0.35"; + shotImg.style.transition = "transform 180ms ease, opacity 180ms ease"; + shotImg.style.transform = "translateX(" + (-dir * 32) + "px)"; + shotImg.style.opacity = "0.35"; window.setTimeout(function () { - setSlide(target); - slideImg.style.transition = "none"; - slideImg.style.transform = "translateX(" + (dir * 32) + "px)"; - void slideImg.offsetWidth; - slideImg.style.transition = "transform 200ms ease, opacity 200ms ease"; - slideImg.style.transform = "translateX(0)"; - slideImg.style.opacity = "1"; + winIndex = target; + paintSlide(); + shotImg.style.transition = "none"; + shotImg.style.transform = "translateX(" + (dir * 32) + "px)"; + void shotImg.offsetWidth; + shotImg.style.transition = "transform 200ms ease, opacity 200ms ease"; + shotImg.style.transform = "translateX(0)"; + shotImg.style.opacity = "1"; window.setTimeout(function () { animating = false; - slideImg.style.transition = ""; - slideImg.style.transform = ""; - slideImg.style.opacity = ""; + shotImg.style.transition = ""; + shotImg.style.transform = ""; + shotImg.style.opacity = ""; }, 220); }, 180); } - // Arrow keys move between screenshots, the way a tablist behaves. - thumbs.addEventListener("keydown", function (e) { - var keys = ["ArrowLeft", "ArrowRight", "Home", "End"]; - if (keys.indexOf(e.key) === -1) return; - e.preventDefault(); - var n = activeSlides().length; - var next = e.key === "Home" ? 0 : e.key === "End" ? n - 1 : slideIndex + (e.key === "ArrowRight" ? 1 : -1); - showSlide(next, e.key === "ArrowLeft" || e.key === "Home" ? -1 : 1); - var btn = thumbs.children[mod(next, n)]; - if (btn) btn.focus(); + windowButtons.forEach(function (b) { + b.addEventListener("click", function () { showWindow(Number(b.getAttribute("data-window"))); }); + }); + themeButtons.forEach(function (b) { + b.addEventListener("click", function () { + shotTheme = b.getAttribute("data-shot-theme"); + paintSlide(); + }); }); + document.getElementById("slidePrev").addEventListener("click", function () { showWindow(winIndex - 1, -1); }); + document.getElementById("slideNext").addEventListener("click", function () { showWindow(winIndex + 1, 1); }); - document.getElementById("slidePrev").addEventListener("click", function () { showSlide(slideIndex - 1, -1); }); - document.getElementById("slideNext").addEventListener("click", function () { showSlide(slideIndex + 1, 1); }); + // Arrow keys move between windows when the tabs have focus. + [].slice.call(document.querySelectorAll(".window-tabs, .theme-tabs")).forEach(function (group) { + group.addEventListener("keydown", function (e) { + if (["ArrowLeft", "ArrowRight", "Home", "End"].indexOf(e.key) === -1) return; + e.preventDefault(); + if (group.classList.contains("window-tabs")) { + var n = WINDOWS.length; + var next = e.key === "Home" ? 0 : e.key === "End" ? n - 1 : winIndex + (e.key === "ArrowRight" ? 1 : -1); + showWindow(next, e.key === "ArrowLeft" || e.key === "Home" ? -1 : 1); + windowButtons[mod(next, n)].focus(); + } else { + shotTheme = e.key === "ArrowLeft" || e.key === "Home" ? "light" : "dark"; + paintSlide(); + (shotTheme === "light" ? themeButtons[0] : themeButtons[1]).focus(); + } + }); + }); - // ---------- Swipe del slider (tap y arrastre) ---------- - var slideViewport = document.querySelector(".slider-viewport"); + // ---------- Swipe (tap y arrastre) ---------- var drag = null; var suppressLightbox = false; function resetSlideImage() { - slideImg.style.transition = "transform 200ms ease, opacity 200ms ease"; - slideImg.style.transform = "translateX(0)"; - slideImg.style.opacity = "1"; + shotImg.style.transition = "transform 200ms ease, opacity 200ms ease"; + shotImg.style.transform = "translateX(0)"; + shotImg.style.opacity = "1"; } - slideViewport.addEventListener("pointerdown", function (e) { + shotViewport.addEventListener("pointerdown", function (e) { if (e.button !== 0 || animating) return; drag = { x: e.clientX, y: e.clientY, dx: 0, active: false, id: e.pointerId }; }); - - slideViewport.addEventListener("pointermove", function (e) { + shotViewport.addEventListener("pointermove", function (e) { if (!drag || e.pointerId !== drag.id) return; var dx = e.clientX - drag.x; var dy = e.clientY - drag.y; @@ -171,15 +195,14 @@ if (Math.abs(dx) < 8 && Math.abs(dy) < 8) return; if (Math.abs(dy) > Math.abs(dx)) { drag = null; return; } drag.active = true; - slideViewport.setPointerCapture(e.pointerId); - slideImg.style.transition = "none"; + shotViewport.setPointerCapture(e.pointerId); + shotImg.style.transition = "none"; } drag.dx = dx; - slideImg.style.transform = "translateX(" + dx + "px)"; - slideImg.style.opacity = String(Math.max(0.55, 1 - Math.abs(dx) / 600)); + shotImg.style.transform = "translateX(" + dx + "px)"; + shotImg.style.opacity = String(Math.max(0.55, 1 - Math.abs(dx) / 600)); e.preventDefault(); }); - function endDrag() { if (!drag) return; var d = drag; @@ -187,44 +210,26 @@ if (!d.active) return; suppressLightbox = true; window.setTimeout(function () { suppressLightbox = false; }, 300); - var threshold = Math.min(70, slideViewport.clientWidth * 0.18); + var threshold = Math.min(70, shotViewport.clientWidth * 0.18); if (Math.abs(d.dx) > threshold) { var dir = d.dx < 0 ? 1 : -1; - showSlide(slideIndex + dir, dir); + showWindow(winIndex + dir, dir); } else { resetSlideImage(); } } - - slideViewport.addEventListener("pointerup", endDrag); - slideViewport.addEventListener("pointercancel", function () { + shotViewport.addEventListener("pointerup", endDrag); + shotViewport.addEventListener("pointercancel", function () { if (drag && drag.active) resetSlideImage(); drag = null; }); - document.addEventListener("keydown", function (e) { - if (lightbox.open) return; - if (e.key === "ArrowLeft") showSlide(slideIndex - 1); - if (e.key === "ArrowRight") showSlide(slideIndex + 1); - }); - - // Al cambiar de idioma o de tema, la diapositiva acompaña. - document.addEventListener("langchange", function () { - slideIndex = currentTheme() === "dark" ? 1 : 0; - renderThumbs(); showSlide(slideIndex); - }); - document.addEventListener("themechange", function (e) { - showSlide(e.detail.theme === "dark" ? 1 : 0); - }); - - slideIndex = currentTheme() === "dark" ? 1 : 0; - renderThumbs(); showSlide(slideIndex); - // ---------- Lightbox ---------- - slideImg.addEventListener("click", function () { + shotImg.addEventListener("click", function () { if (suppressLightbox) { suppressLightbox = false; return; } - lightboxImg.src = activeSlides()[slideIndex].full; - lightboxImg.alt = slideImg.alt; + lightboxImg.src = path(winIndex, shotTheme, ".webp"); + lightboxImg.alt = altKey(winIndex); + lightboxCaption.textContent = captionKey(winIndex, shotTheme); lightbox.showModal(); }); document.getElementById("lightboxClose").addEventListener("click", function () { lightbox.close(); }); @@ -232,13 +237,59 @@ if (e.target === lightbox) lightbox.close(); }); + // The site theme drives the screenshot theme, keeping the window on screen. + document.addEventListener("themechange", function (e) { + shotTheme = e.detail.theme; + paintSlide(); + }); + document.addEventListener("langchange", function () { paintSlide(); }); + paintSlide(); + + // ---------- Escena de sincronizacion ---------- + var scene = document.getElementById("syncScene"); + var track = document.getElementById("syncTrack"); + var motionBtn = document.getElementById("motionToggle"); + + function layoutScene() { + var line = track.querySelector(".sync-lane-line"); + var start = line ? line.offsetLeft : 0; + var travel = Math.max(24, track.clientWidth - start - 48); + scene.style.setProperty("--travel", travel + "px"); + scene.style.setProperty("--travel-half", travel / 2 + "px"); + } + if (window.ResizeObserver) new ResizeObserver(layoutScene).observe(track); + layoutScene(); + + var paused = reduceMotion.matches; + function updateMotion() { + scene.classList.toggle("is-paused", paused); + motionBtn.classList.toggle("is-paused", paused); + motionBtn.disabled = reduceMotion.matches; + var d = dict(); + var text = reduceMotion.matches ? d["sync.paused"] : (paused ? d["sync.resume"] : d["sync.pause"]); + var label = reduceMotion.matches ? d["sync.reducedLabel"] : (paused ? d["sync.resumeLabel"] : d["sync.pauseLabel"]); + motionBtn.querySelector("span").textContent = text; + motionBtn.setAttribute("aria-label", label); + } + motionBtn.addEventListener("click", function () { + paused = !paused; + updateMotion(); + }); + document.addEventListener("langchange", updateMotion); + updateMotion(); + // ---------- Copiar comando ---------- var copyBtn = document.getElementById("copyBtn"); + var copyLabel = copyBtn.querySelector("span"); copyBtn.addEventListener("click", function () { var text = document.getElementById("installCmd").textContent; function done() { - copyBtn.textContent = (I18N[lang] || I18N.es)["install.copied"]; - setTimeout(function () { copyBtn.textContent = (I18N[lang] || I18N.es)["install.copy"]; }, 2000); + copyBtn.classList.add("is-copied"); + copyLabel.textContent = dict()["install.copied"]; + window.setTimeout(function () { + copyBtn.classList.remove("is-copied"); + copyLabel.textContent = dict()["install.copy"]; + }, 2000); } if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(done, function () { legacyCopy(text); done(); }); @@ -253,27 +304,4 @@ try { document.execCommand("copy"); } catch (e) { /* sin portapapeles */ } document.body.removeChild(ta); } - - // ---------- Reveal on scroll ---------- - var io = new IntersectionObserver(function (entries) { - entries.forEach(function (en) { - if (en.isIntersecting) { en.target.classList.add("in"); io.unobserve(en.target); } - }); - }, { threshold: 0.12 }); - document.querySelectorAll(".reveal").forEach(function (el) { io.observe(el); }); - - // ---------- Scrollspy ---------- - var navAnchors = [...document.querySelectorAll(".nav-links a[href^='#']")]; - var sections = navAnchors - .map(function (a) { return document.querySelector(a.getAttribute("href")); }) - .filter(Boolean); - var spy = new IntersectionObserver(function (entries) { - entries.forEach(function (en) { - if (!en.isIntersecting) return; - navAnchors.forEach(function (a) { - a.classList.toggle("active", a.getAttribute("href") === "#" + en.target.id); - }); - }); - }, { rootMargin: "-40% 0px -55% 0px" }); - sections.forEach(function (s) { spy.observe(s); }); })(); diff --git a/landing/assets/fonts/OFL-Inter.txt b/landing/assets/fonts/OFL-IBMPlexSans.txt similarity index 96% rename from landing/assets/fonts/OFL-Inter.txt rename to landing/assets/fonts/OFL-IBMPlexSans.txt index 9b2ca37..2ae8ef3 100644 --- a/landing/assets/fonts/OFL-Inter.txt +++ b/landing/assets/fonts/OFL-IBMPlexSans.txt @@ -1,9 +1,10 @@ -Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) +Copyright 2019 IBM Corp. All rights reserved. IBMPlexSans-Italic[wdth,wght].ttf: Copyright 2019 IBM Corp. All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- @@ -43,7 +44,7 @@ new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. -PERMISSION AND CONDITIONS +PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font diff --git a/landing/assets/fonts/OFL-IBMPlexSerif.txt b/landing/assets/fonts/OFL-IBMPlexSerif.txt new file mode 100644 index 0000000..10c3093 --- /dev/null +++ b/landing/assets/fonts/OFL-IBMPlexSerif.txt @@ -0,0 +1,93 @@ +Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-ThinItalic.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-ExtraLight.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-ExtraLightItalic.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-Light.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-LightItalic.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-Regular.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-Italic.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-Medium.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-MediumItalic.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-SemiBold.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-SemiBoldItalic.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-Bold.ttf: Copyright 2020 IBM Corp. All rights reserved. IBMPlexSerif-BoldItalic.ttf: Copyright 2020 IBM Corp. All rights reserved. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/landing/assets/fonts/ibm-plex-sans-latin.woff2 b/landing/assets/fonts/ibm-plex-sans-latin.woff2 new file mode 100644 index 0000000..b757bc5 Binary files /dev/null and b/landing/assets/fonts/ibm-plex-sans-latin.woff2 differ diff --git a/landing/assets/fonts/ibm-plex-serif-latin-italic.woff2 b/landing/assets/fonts/ibm-plex-serif-latin-italic.woff2 new file mode 100644 index 0000000..cbc83ff Binary files /dev/null and b/landing/assets/fonts/ibm-plex-serif-latin-italic.woff2 differ diff --git a/landing/assets/fonts/ibm-plex-serif-latin.woff2 b/landing/assets/fonts/ibm-plex-serif-latin.woff2 new file mode 100644 index 0000000..99a12f3 Binary files /dev/null and b/landing/assets/fonts/ibm-plex-serif-latin.woff2 differ diff --git a/landing/assets/fonts/inter-latin.woff2 b/landing/assets/fonts/inter-latin.woff2 deleted file mode 100644 index d15208d..0000000 Binary files a/landing/assets/fonts/inter-latin.woff2 and /dev/null differ diff --git a/landing/assets/shots/activity-en-dark-1080.webp b/landing/assets/shots/activity-en-dark-1080.webp new file mode 100644 index 0000000..3ec613b Binary files /dev/null and b/landing/assets/shots/activity-en-dark-1080.webp differ diff --git a/landing/assets/shots/activity-en-dark-320.webp b/landing/assets/shots/activity-en-dark-320.webp new file mode 100644 index 0000000..44c4b46 Binary files /dev/null and b/landing/assets/shots/activity-en-dark-320.webp differ diff --git a/landing/assets/shots/activity-en-dark-640.webp b/landing/assets/shots/activity-en-dark-640.webp new file mode 100644 index 0000000..53c44cd Binary files /dev/null and b/landing/assets/shots/activity-en-dark-640.webp differ diff --git a/landing/assets/shots/activity-en-dark-960.webp b/landing/assets/shots/activity-en-dark-960.webp new file mode 100644 index 0000000..debc6ac Binary files /dev/null and b/landing/assets/shots/activity-en-dark-960.webp differ diff --git a/landing/assets/shots/activity-en-dark.webp b/landing/assets/shots/activity-en-dark.webp new file mode 100644 index 0000000..f25832c Binary files /dev/null and b/landing/assets/shots/activity-en-dark.webp differ diff --git a/landing/assets/shots/activity-en-light-1080.webp b/landing/assets/shots/activity-en-light-1080.webp new file mode 100644 index 0000000..4270d0f Binary files /dev/null and b/landing/assets/shots/activity-en-light-1080.webp differ diff --git a/landing/assets/shots/activity-en-light-320.webp b/landing/assets/shots/activity-en-light-320.webp new file mode 100644 index 0000000..8524a1c Binary files /dev/null and b/landing/assets/shots/activity-en-light-320.webp differ diff --git a/landing/assets/shots/activity-en-light-640.webp b/landing/assets/shots/activity-en-light-640.webp new file mode 100644 index 0000000..21741c3 Binary files /dev/null and b/landing/assets/shots/activity-en-light-640.webp differ diff --git a/landing/assets/shots/activity-en-light-960.webp b/landing/assets/shots/activity-en-light-960.webp new file mode 100644 index 0000000..33f398a Binary files /dev/null and b/landing/assets/shots/activity-en-light-960.webp differ diff --git a/landing/assets/shots/activity-en-light.webp b/landing/assets/shots/activity-en-light.webp new file mode 100644 index 0000000..5ac6cde Binary files /dev/null and b/landing/assets/shots/activity-en-light.webp differ diff --git a/landing/assets/shots/activity-es-dark-1080.webp b/landing/assets/shots/activity-es-dark-1080.webp new file mode 100644 index 0000000..bfe3946 Binary files /dev/null and b/landing/assets/shots/activity-es-dark-1080.webp differ diff --git a/landing/assets/shots/activity-es-dark-320.webp b/landing/assets/shots/activity-es-dark-320.webp new file mode 100644 index 0000000..b686c1f Binary files /dev/null and b/landing/assets/shots/activity-es-dark-320.webp differ diff --git a/landing/assets/shots/activity-es-dark-640.webp b/landing/assets/shots/activity-es-dark-640.webp new file mode 100644 index 0000000..35fcfca Binary files /dev/null and b/landing/assets/shots/activity-es-dark-640.webp differ diff --git a/landing/assets/shots/activity-es-dark-960.webp b/landing/assets/shots/activity-es-dark-960.webp new file mode 100644 index 0000000..5c06a0a Binary files /dev/null and b/landing/assets/shots/activity-es-dark-960.webp differ diff --git a/landing/assets/shots/activity-es-dark.webp b/landing/assets/shots/activity-es-dark.webp new file mode 100644 index 0000000..dbeb218 Binary files /dev/null and b/landing/assets/shots/activity-es-dark.webp differ diff --git a/landing/assets/shots/activity-es-light-1080.webp b/landing/assets/shots/activity-es-light-1080.webp new file mode 100644 index 0000000..0ca1b31 Binary files /dev/null and b/landing/assets/shots/activity-es-light-1080.webp differ diff --git a/landing/assets/shots/activity-es-light-320.webp b/landing/assets/shots/activity-es-light-320.webp new file mode 100644 index 0000000..b0fd93a Binary files /dev/null and b/landing/assets/shots/activity-es-light-320.webp differ diff --git a/landing/assets/shots/activity-es-light-640.webp b/landing/assets/shots/activity-es-light-640.webp new file mode 100644 index 0000000..f6131e4 Binary files /dev/null and b/landing/assets/shots/activity-es-light-640.webp differ diff --git a/landing/assets/shots/activity-es-light-960.webp b/landing/assets/shots/activity-es-light-960.webp new file mode 100644 index 0000000..4e5e012 Binary files /dev/null and b/landing/assets/shots/activity-es-light-960.webp differ diff --git a/landing/assets/shots/activity-es-light.webp b/landing/assets/shots/activity-es-light.webp new file mode 100644 index 0000000..70b57b3 Binary files /dev/null and b/landing/assets/shots/activity-es-light.webp differ diff --git a/landing/assets/shots/main-en-dark-1080.webp b/landing/assets/shots/main-en-dark-1080.webp index 7be02ea..18d11d2 100644 Binary files a/landing/assets/shots/main-en-dark-1080.webp and b/landing/assets/shots/main-en-dark-1080.webp differ diff --git a/landing/assets/shots/main-en-dark-320.webp b/landing/assets/shots/main-en-dark-320.webp index 6e686f0..0d36207 100644 Binary files a/landing/assets/shots/main-en-dark-320.webp and b/landing/assets/shots/main-en-dark-320.webp differ diff --git a/landing/assets/shots/main-en-dark-640.webp b/landing/assets/shots/main-en-dark-640.webp index 8e6b886..42fbe8c 100644 Binary files a/landing/assets/shots/main-en-dark-640.webp and b/landing/assets/shots/main-en-dark-640.webp differ diff --git a/landing/assets/shots/main-en-dark-960.webp b/landing/assets/shots/main-en-dark-960.webp index a417d34..50e63d8 100644 Binary files a/landing/assets/shots/main-en-dark-960.webp and b/landing/assets/shots/main-en-dark-960.webp differ diff --git a/landing/assets/shots/main-en-dark.webp b/landing/assets/shots/main-en-dark.webp index 0345155..563344a 100644 Binary files a/landing/assets/shots/main-en-dark.webp and b/landing/assets/shots/main-en-dark.webp differ diff --git a/landing/assets/shots/main-en-light-1080.webp b/landing/assets/shots/main-en-light-1080.webp index 82743a4..296958e 100644 Binary files a/landing/assets/shots/main-en-light-1080.webp and b/landing/assets/shots/main-en-light-1080.webp differ diff --git a/landing/assets/shots/main-en-light-320.webp b/landing/assets/shots/main-en-light-320.webp index 001fd27..89b351e 100644 Binary files a/landing/assets/shots/main-en-light-320.webp and b/landing/assets/shots/main-en-light-320.webp differ diff --git a/landing/assets/shots/main-en-light-640.webp b/landing/assets/shots/main-en-light-640.webp index 10c46ca..7dd3e59 100644 Binary files a/landing/assets/shots/main-en-light-640.webp and b/landing/assets/shots/main-en-light-640.webp differ diff --git a/landing/assets/shots/main-en-light-960.webp b/landing/assets/shots/main-en-light-960.webp index 5168df1..c3ef9a0 100644 Binary files a/landing/assets/shots/main-en-light-960.webp and b/landing/assets/shots/main-en-light-960.webp differ diff --git a/landing/assets/shots/main-en-light.webp b/landing/assets/shots/main-en-light.webp index 0d3ff60..04bf011 100644 Binary files a/landing/assets/shots/main-en-light.webp and b/landing/assets/shots/main-en-light.webp differ diff --git a/landing/assets/shots/main-es-dark-1080.webp b/landing/assets/shots/main-es-dark-1080.webp index 13fe948..56a2ea9 100644 Binary files a/landing/assets/shots/main-es-dark-1080.webp and b/landing/assets/shots/main-es-dark-1080.webp differ diff --git a/landing/assets/shots/main-es-dark-320.webp b/landing/assets/shots/main-es-dark-320.webp index 9cf4da5..027b343 100644 Binary files a/landing/assets/shots/main-es-dark-320.webp and b/landing/assets/shots/main-es-dark-320.webp differ diff --git a/landing/assets/shots/main-es-dark-640.webp b/landing/assets/shots/main-es-dark-640.webp index 847a69a..916b4f0 100644 Binary files a/landing/assets/shots/main-es-dark-640.webp and b/landing/assets/shots/main-es-dark-640.webp differ diff --git a/landing/assets/shots/main-es-dark-960.webp b/landing/assets/shots/main-es-dark-960.webp index a7c50b9..7826923 100644 Binary files a/landing/assets/shots/main-es-dark-960.webp and b/landing/assets/shots/main-es-dark-960.webp differ diff --git a/landing/assets/shots/main-es-dark.webp b/landing/assets/shots/main-es-dark.webp index 12d64d3..ab7ac67 100644 Binary files a/landing/assets/shots/main-es-dark.webp and b/landing/assets/shots/main-es-dark.webp differ diff --git a/landing/assets/shots/main-es-light-1080.webp b/landing/assets/shots/main-es-light-1080.webp index 703cb14..91e1bbd 100644 Binary files a/landing/assets/shots/main-es-light-1080.webp and b/landing/assets/shots/main-es-light-1080.webp differ diff --git a/landing/assets/shots/main-es-light-320.webp b/landing/assets/shots/main-es-light-320.webp index 498d84c..f1464b6 100644 Binary files a/landing/assets/shots/main-es-light-320.webp and b/landing/assets/shots/main-es-light-320.webp differ diff --git a/landing/assets/shots/main-es-light-640.webp b/landing/assets/shots/main-es-light-640.webp index fe6ae9f..be40230 100644 Binary files a/landing/assets/shots/main-es-light-640.webp and b/landing/assets/shots/main-es-light-640.webp differ diff --git a/landing/assets/shots/main-es-light-960.webp b/landing/assets/shots/main-es-light-960.webp index bb67515..3a7687d 100644 Binary files a/landing/assets/shots/main-es-light-960.webp and b/landing/assets/shots/main-es-light-960.webp differ diff --git a/landing/assets/shots/main-es-light.webp b/landing/assets/shots/main-es-light.webp index 21061d1..f76115f 100644 Binary files a/landing/assets/shots/main-es-light.webp and b/landing/assets/shots/main-es-light.webp differ diff --git a/landing/assets/shots/settings-en-dark-1080.webp b/landing/assets/shots/settings-en-dark-1080.webp new file mode 100644 index 0000000..87ecbd4 Binary files /dev/null and b/landing/assets/shots/settings-en-dark-1080.webp differ diff --git a/landing/assets/shots/settings-en-dark-320.webp b/landing/assets/shots/settings-en-dark-320.webp new file mode 100644 index 0000000..3e4341f Binary files /dev/null and b/landing/assets/shots/settings-en-dark-320.webp differ diff --git a/landing/assets/shots/settings-en-dark-640.webp b/landing/assets/shots/settings-en-dark-640.webp new file mode 100644 index 0000000..291ed78 Binary files /dev/null and b/landing/assets/shots/settings-en-dark-640.webp differ diff --git a/landing/assets/shots/settings-en-dark-960.webp b/landing/assets/shots/settings-en-dark-960.webp new file mode 100644 index 0000000..db38709 Binary files /dev/null and b/landing/assets/shots/settings-en-dark-960.webp differ diff --git a/landing/assets/shots/settings-en-dark.webp b/landing/assets/shots/settings-en-dark.webp new file mode 100644 index 0000000..0f8415b Binary files /dev/null and b/landing/assets/shots/settings-en-dark.webp differ diff --git a/landing/assets/shots/settings-en-light-1080.webp b/landing/assets/shots/settings-en-light-1080.webp new file mode 100644 index 0000000..2423ecc Binary files /dev/null and b/landing/assets/shots/settings-en-light-1080.webp differ diff --git a/landing/assets/shots/settings-en-light-320.webp b/landing/assets/shots/settings-en-light-320.webp new file mode 100644 index 0000000..c2a1b8b Binary files /dev/null and b/landing/assets/shots/settings-en-light-320.webp differ diff --git a/landing/assets/shots/settings-en-light-640.webp b/landing/assets/shots/settings-en-light-640.webp new file mode 100644 index 0000000..e305c21 Binary files /dev/null and b/landing/assets/shots/settings-en-light-640.webp differ diff --git a/landing/assets/shots/settings-en-light-960.webp b/landing/assets/shots/settings-en-light-960.webp new file mode 100644 index 0000000..660f2e0 Binary files /dev/null and b/landing/assets/shots/settings-en-light-960.webp differ diff --git a/landing/assets/shots/settings-en-light.webp b/landing/assets/shots/settings-en-light.webp new file mode 100644 index 0000000..4c0f10c Binary files /dev/null and b/landing/assets/shots/settings-en-light.webp differ diff --git a/landing/assets/shots/settings-es-dark-1080.webp b/landing/assets/shots/settings-es-dark-1080.webp new file mode 100644 index 0000000..761db60 Binary files /dev/null and b/landing/assets/shots/settings-es-dark-1080.webp differ diff --git a/landing/assets/shots/settings-es-dark-320.webp b/landing/assets/shots/settings-es-dark-320.webp new file mode 100644 index 0000000..4decc25 Binary files /dev/null and b/landing/assets/shots/settings-es-dark-320.webp differ diff --git a/landing/assets/shots/settings-es-dark-640.webp b/landing/assets/shots/settings-es-dark-640.webp new file mode 100644 index 0000000..35e7282 Binary files /dev/null and b/landing/assets/shots/settings-es-dark-640.webp differ diff --git a/landing/assets/shots/settings-es-dark-960.webp b/landing/assets/shots/settings-es-dark-960.webp new file mode 100644 index 0000000..be503c7 Binary files /dev/null and b/landing/assets/shots/settings-es-dark-960.webp differ diff --git a/landing/assets/shots/settings-es-dark.webp b/landing/assets/shots/settings-es-dark.webp new file mode 100644 index 0000000..1455e99 Binary files /dev/null and b/landing/assets/shots/settings-es-dark.webp differ diff --git a/landing/assets/shots/settings-es-light-1080.webp b/landing/assets/shots/settings-es-light-1080.webp new file mode 100644 index 0000000..b5eebbe Binary files /dev/null and b/landing/assets/shots/settings-es-light-1080.webp differ diff --git a/landing/assets/shots/settings-es-light-320.webp b/landing/assets/shots/settings-es-light-320.webp new file mode 100644 index 0000000..673a593 Binary files /dev/null and b/landing/assets/shots/settings-es-light-320.webp differ diff --git a/landing/assets/shots/settings-es-light-640.webp b/landing/assets/shots/settings-es-light-640.webp new file mode 100644 index 0000000..732a455 Binary files /dev/null and b/landing/assets/shots/settings-es-light-640.webp differ diff --git a/landing/assets/shots/settings-es-light-960.webp b/landing/assets/shots/settings-es-light-960.webp new file mode 100644 index 0000000..772bab9 Binary files /dev/null and b/landing/assets/shots/settings-es-light-960.webp differ diff --git a/landing/assets/shots/settings-es-light.webp b/landing/assets/shots/settings-es-light.webp new file mode 100644 index 0000000..b96f211 Binary files /dev/null and b/landing/assets/shots/settings-es-light.webp differ diff --git a/landing/i18n.js b/landing/i18n.js index 9a02af3..755e51e 100644 --- a/landing/i18n.js +++ b/landing/i18n.js @@ -7,10 +7,13 @@ const I18N = { "misc.theme": "Cambiar tema", "misc.github": "Repositorio en GitHub", "misc.close": "Cerrar", + "misc.menu": "Menú", "nav.features": "Funciones", "nav.shots": "Capturas", "nav.compare": "Comparativa", "nav.install": "Instalar", + "nav.main": "Principal", + "nav.mobile": "Navegación móvil", "hero.eyebrow": "Sincronización para GNOME", "hero.t1": "Tus archivos, en local.", "hero.t2": "Cualquier servidor, en sincronía.", @@ -20,9 +23,21 @@ const I18N = { "hero.fact1": "Binario único en Rust", "hero.fact2": "Nextcloud y OpenCloud", "hero.fact3": "Sin telemetría", + "hero.factsLabel": "Datos clave", "hero.widget": "Animación: los archivos viajan entre tu equipo y tu servidor", "sw.local": "Tu equipo", "sw.server": "Tu servidor", + "sw.localSub": "GNOME · copia local", + "sw.docs": "Docs", + "sw.photos": "Fotos", + "sw.music": "Música", + "sync.live": "Cambios sincronizados en ambos sentidos", + "sync.pause": "Pausar", + "sync.resume": "Reanudar", + "sync.paused": "Movimiento reducido", + "sync.pauseLabel": "Pausar animación", + "sync.resumeLabel": "Reanudar animación", + "sync.reducedLabel": "Movimiento reducido activado", "pillars.title": "Construido sobre tres principios", "pillars.lead": "No son eslóganes de marketing. Condicionan cada decisión del proyecto.", "pillars.p1t": "Agnóstico de servidor", @@ -32,6 +47,8 @@ const I18N = { "pillars.p3t": "Privado por diseño", "pillars.p3d": "Sin telemetría, sin analítica, sin informes de fallos remotos. Las credenciales viven en GNOME Keyring a través de Secret Service.", "features.title": "Lo que hace por ti", + "features.lead": "Una capa de escritorio cuidada sobre los motores oficiales. Ni más, ni menos.", + "features.seeUi": "Ver la interfaz", "features.f1t": "Multi-cuenta y multi-carpeta", "features.f1d": "Cada cuenta mantiene sus propias carpetas, y cada carpeta tiene su ruta remota (o su space de OpenCloud), su estado y sus disparadores.", "features.f2t": "Detección rápida de cambios", @@ -45,29 +62,46 @@ const I18N = { "features.f6t": "Login seguro", "features.f6d": "Login Flow v2 por navegador en Nextcloud, app password en OpenCloud. Las credenciales se guardan en el llavero del sistema.", "shots.title": "Un vistazo a la interfaz", - "shots.lead": "Capturas reales de la app, en los dos idiomas y los dos temas.", + "shots.lead": "Tres ventanas reales de la app, en los dos idiomas y los dos temas.", "shots.prev": "Captura anterior", "shots.next": "Captura siguiente", "shots.zoom": "Captura ampliada", - "shots.capLight": "Ventana principal, tema claro", - "shots.capDark": "Ventana principal, tema oscuro", - "shots.alt": "Ventana principal de NextSync", + "shots.windowsLabel": "Ventana", + "shots.themeLabel": "Tema", + "shots.winMain": "Principal", + "shots.winActivity": "Actividad", + "shots.winSettings": "Ajustes", + "shots.themeLight": "Claro", + "shots.themeDark": "Oscuro", + "shots.capMainLight": "Ventana principal, tema claro", + "shots.capMainDark": "Ventana principal, tema oscuro", + "shots.capActivityLight": "Actividad de sincronización, tema claro", + "shots.capActivityDark": "Actividad de sincronización, tema oscuro", + "shots.capSettingsLight": "Preferencias de sincronización, tema claro", + "shots.capSettingsDark": "Preferencias de sincronización, tema oscuro", + "shots.altMain": "Ventana principal de NextSync con dos cuentas y sus carpetas", + "shots.altActivity": "Historial de actividad de sincronización de NextSync", + "shots.altSettings": "Preferencias de sincronización de NextSync", "compare.title": "El compañero GNOME que faltaba", "compare.tableLabel": "Tabla comparativa", + "compare.hint": "Desliza horizontalmente para ver todos los clientes", "compare.lead": "Comparativa con los clientes de escritorio oficiales, contrastada el 20 de agosto de 2026 contra sus repos públicos.", "compare.h0": "Función", + "compare.yes": "Sí", + "compare.no": "No", + "compare.partial": "Parcial", "compare.r1": "Interfaz nativa GNOME (GTK 4 / Libadwaita)", - "compare.qt1": "✗ (Qt)", - "compare.qt2": "✗ (Qt)", + "compare.qt1": "(Qt)", + "compare.qt2": "(Qt)", "compare.r2": "Multi-proveedor en una sola app", "compare.r3": "Motor de sync = CLI oficial del servidor", - "compare.own1": "◐ (motor propio)", - "compare.own2": "◐ (motor propio)", + "compare.own1": "(motor propio)", + "compare.own2": "(motor propio)", "compare.r4": "Archivos virtuales bajo demanda", - "compare.mirror": "✗ (espejo completo)", + "compare.mirror": "(espejo completo)", "compare.r5": "Guardia de borrado masivo", - "compare.opt1": "◐ (opt-in, solo GUI)", - "compare.opt2": "◐ (opt-in, solo GUI)", + "compare.opt1": "(opt-in, solo GUI)", + "compare.opt2": "(opt-in, solo GUI)", "compare.r6": "Credenciales en el llavero del sistema", "compare.ht": "Lo que NextSync no hace (todavía)", "compare.h1": "No hay archivos virtuales: descarga copias completas de las carpetas que elijas.", @@ -77,6 +111,7 @@ const I18N = { "install.mTitle": "Instalación", "install.mBody": "NextSync es una aplicación de escritorio para Linux, pensada para GNOME. No hay versión para Android ni iOS: la instalación se hace en el ordenador.", "install.lead": "En Arch, CachyOS y derivadas hay paquete listo en cada release.", + "install.download": "Descargar release", "install.copy": "Copiar comando", "install.copied": "Copiado al portapapeles", "install.note": "Descarga el paquete .pkg.tar.zst más reciente desde GitHub Releases y ajusta el nombre del fichero.", @@ -87,6 +122,7 @@ const I18N = { "about.title": "Por qué existe", "about.p1": "NextSync nació de una necesidad concreta: mantener espejos locales completos de varias cuentas Nextcloud y OpenCloud en el escritorio, sin depender de un cliente que solo hable con un servidor. Está escrito en Rust: un binario único, arranque instantáneo y una arquitectura que no ata el proyecto a un proveedor.", "about.p2": "Es un proyecto personal, GPL-3.0+, gratuito siempre. Corre 24/7 en producción propia y cada versión se prueba ahí primero. Con colaboraciones o apoyo quizás podría crecer más rápido, pero no puedo prometer nada.", + "about.code": "Explorar el código", "faq.q1": "¿Es un producto oficial de Nextcloud?", "faq.a1": "No. NextSync es un proyecto independiente y no oficial, sin afiliación con Nextcloud GmbH ni con el Heinlein Group. Nextcloud es una marca registrada de Nextcloud GmbH.", "faq.q2": "¿Soporta archivos virtuales?", @@ -107,10 +143,13 @@ const I18N = { "misc.theme": "Toggle theme", "misc.github": "GitHub repository", "misc.close": "Close", + "misc.menu": "Menu", "nav.features": "Features", "nav.shots": "Screenshots", "nav.compare": "Compare", "nav.install": "Install", + "nav.main": "Main", + "nav.mobile": "Mobile navigation", "hero.eyebrow": "Synchronization for GNOME", "hero.t1": "Your files, local.", "hero.t2": "Any server, in sync.", @@ -120,9 +159,21 @@ const I18N = { "hero.fact1": "Single Rust binary", "hero.fact2": "Nextcloud and OpenCloud", "hero.fact3": "No telemetry", + "hero.factsLabel": "Key facts", "hero.widget": "Animation: files travel between your computer and your server", "sw.local": "Your computer", "sw.server": "Your server", + "sw.localSub": "GNOME · local mirror", + "sw.docs": "Docs", + "sw.photos": "Photos", + "sw.music": "Music", + "sync.live": "Changes synced in both directions", + "sync.pause": "Pause", + "sync.resume": "Resume", + "sync.paused": "Reduced motion", + "sync.pauseLabel": "Pause animation", + "sync.resumeLabel": "Resume animation", + "sync.reducedLabel": "Reduced motion enabled", "pillars.title": "Built on three principles", "pillars.lead": "Not marketing slogans. They shape every decision in the project.", "pillars.p1t": "Server agnostic", @@ -132,6 +183,8 @@ const I18N = { "pillars.p3t": "Private by design", "pillars.p3d": "No telemetry, no analytics, no remote crash reporting. Credentials live in GNOME Keyring through Secret Service.", "features.title": "What it does for you", + "features.lead": "A thoughtful desktop layer on top of the official engines. No more, no less.", + "features.seeUi": "See the interface", "features.f1t": "Multi-account and multi-folder", "features.f1d": "Each account keeps its own folders, and each folder has its own remote path (or OpenCloud space), its own status and its own triggers.", "features.f2t": "Fast change detection", @@ -145,29 +198,46 @@ const I18N = { "features.f6t": "Secure login", "features.f6d": "Login Flow v2 in the browser for Nextcloud, app password for OpenCloud. Credentials are stored in the system keyring.", "shots.title": "A look at the interface", - "shots.lead": "Real screenshots of the app, in both languages and both themes.", + "shots.lead": "Three real windows of the app, in both languages and both themes.", "shots.prev": "Previous screenshot", "shots.next": "Next screenshot", "shots.zoom": "Enlarged screenshot", - "shots.capLight": "Main window, light theme", - "shots.capDark": "Main window, dark theme", - "shots.alt": "NextSync main window", + "shots.windowsLabel": "Window", + "shots.themeLabel": "Theme", + "shots.winMain": "Main", + "shots.winActivity": "Activity", + "shots.winSettings": "Settings", + "shots.themeLight": "Light", + "shots.themeDark": "Dark", + "shots.capMainLight": "Main window, light theme", + "shots.capMainDark": "Main window, dark theme", + "shots.capActivityLight": "Sync activity, light theme", + "shots.capActivityDark": "Sync activity, dark theme", + "shots.capSettingsLight": "Sync settings, light theme", + "shots.capSettingsDark": "Sync settings, dark theme", + "shots.altMain": "NextSync main window with two accounts and their folders", + "shots.altActivity": "NextSync sync activity history", + "shots.altSettings": "NextSync sync settings", "compare.title": "The GNOME companion that was missing", "compare.tableLabel": "Comparison table", + "compare.hint": "Scroll horizontally to see all clients", "compare.lead": "Compared with the official desktop clients, checked on August 20, 2026 against their public repos.", "compare.h0": "Feature", + "compare.yes": "Yes", + "compare.no": "No", + "compare.partial": "Partial", "compare.r1": "GNOME-native interface (GTK 4 / Libadwaita)", - "compare.qt1": "✗ (Qt)", - "compare.qt2": "✗ (Qt)", + "compare.qt1": "(Qt)", + "compare.qt2": "(Qt)", "compare.r2": "Multi-provider in a single app", "compare.r3": "Sync engine = the official server CLI", - "compare.own1": "◐ (own engine)", - "compare.own2": "◐ (own engine)", + "compare.own1": "(own engine)", + "compare.own2": "(own engine)", "compare.r4": "On-demand virtual files", - "compare.mirror": "✗ (full mirror)", + "compare.mirror": "(full mirror)", "compare.r5": "Mass deletion guard", - "compare.opt1": "◐ (opt-in, GUI only)", - "compare.opt2": "◐ (opt-in, GUI only)", + "compare.opt1": "(opt-in, GUI only)", + "compare.opt2": "(opt-in, GUI only)", "compare.r6": "Credentials in the system keyring", "compare.ht": "What NextSync does not do (yet)", "compare.h1": "No virtual files: it downloads full copies of the folders you choose.", @@ -177,6 +247,7 @@ const I18N = { "install.mTitle": "Installation", "install.mBody": "NextSync is a Linux desktop application, designed for GNOME. There is no Android or iOS version: installation happens on the computer.", "install.lead": "On Arch, CachyOS and derivatives there is a ready package in every release.", + "install.download": "Download release", "install.copy": "Copy command", "install.copied": "Copied to clipboard", "install.note": "Download the latest .pkg.tar.zst package from GitHub Releases and adjust the file name.", @@ -187,6 +258,7 @@ const I18N = { "about.title": "Why it exists", "about.p1": "NextSync started from a concrete need: keeping full local mirrors of several Nextcloud and OpenCloud accounts on the desktop, without depending on a client that only speaks to one server. It is written in Rust: a single binary, near instant startup and an architecture that does not tie the project to one vendor.", "about.p2": "It is a personal project, GPL-3.0+, free forever. It runs 24/7 on the author's own production setup and every release is tested there first. With contributions or support it might grow faster, but I cannot promise anything.", + "about.code": "Explore the code", "faq.q1": "Is it an official Nextcloud product?", "faq.a1": "No. NextSync is an independent, unofficial project with no affiliation to Nextcloud GmbH or the Heinlein Group. Nextcloud is a registered trademark of Nextcloud GmbH.", "faq.q2": "Does it support virtual files?", @@ -230,6 +302,13 @@ function applyLang(lang) { if (dict["meta.title"]) document.title = dict["meta.title"]; const metaDesc = document.querySelector('meta[name="description"]'); if (metaDesc && dict["meta.description"]) metaDesc.setAttribute("content", dict["meta.description"]); + // Each language is reachable at its own URL, so the canonical and og:url + // must point at the variant being shown, not always at the Spanish one. + const canonicalUrl = "https://nextsync.cloudless.club/" + (lang === "en" ? "?hl=en" : ""); + const canonical = document.querySelector('link[rel="canonical"]'); + if (canonical) canonical.setAttribute("href", canonicalUrl); + const ogUrl = document.querySelector('meta[property="og:url"]'); + if (ogUrl) ogUrl.setAttribute("content", canonicalUrl); localStorage.setItem("nextsync-lang", lang); document.dispatchEvent(new CustomEvent("langchange", { detail: { lang } })); } diff --git a/landing/index.html b/landing/index.html index b8ef378..3fb55c2 100644 --- a/landing/index.html +++ b/landing/index.html @@ -18,16 +18,19 @@ + + - - - + + + + + + - +
-
- -
- Sincronización para GNOME -

Tus archivos, en local.
Cualquier servidor, en sincronía.

-

NextSync es una app de escritorio para GNOME que mantiene espejos locales completos de tus cuentas Nextcloud y OpenCloud. Sin nube propia, sin telemetría, sin suscripción.

+
+
+
+ + Sincronización para GNOME + GTK 4 / Rust +
+

Tus archivos, en local.
Cualquier servidor, en sincronía.

+

NextSync es una app de escritorio para GNOME que mantiene espejos locales completos de tus cuentas Nextcloud y OpenCloud. Sin nube propia, sin telemetría, sin suscripción.

-
    -
  • Binario único en Rust
  • -
  • GPL-3.0+
  • -
  • Nextcloud y OpenCloud
  • -
  • Sin telemetría
  • -
+
-
-
-

Construido sobre tres principios

-

No son eslóganes de marketing. Condicionan cada decisión del proyecto.

-
-
-

Agnóstico de servidor

-

NextSync no habla el protocolo de un fabricante: delega la sincronización en el CLI oficial del servidor (nextcloudcmd, opencloudcmd). La experiencia de escritorio es la misma para todos.

+
    +
  • Binario único en Rust
  • +
  • GPL-3.0+
  • +
  • Nextcloud y OpenCloud
  • +
  • Sin telemetría
  • +
+
+ +
+
+

Construido sobre tres principios

+

No son eslóganes de marketing. Condicionan cada decisión del proyecto.

+
+
+
+ +

Agnóstico de servidor

NextSync no habla el protocolo de un fabricante: delega la sincronización en el CLI oficial del servidor (nextcloudcmd, opencloudcmd). La experiencia de escritorio es la misma para todos.

-
-

El motor oficial hace el trabajo

-

La sincronización, los conflictos y la seguridad son del motor CLI. NextSync añade la capa GNOME y cubre lo que el CLI deja abierto, como la guardia de borrados masivos.

+
+ +

El motor oficial hace el trabajo

La sincronización, los conflictos y la seguridad son del motor CLI. NextSync añade la capa GNOME y cubre lo que el CLI deja abierto, como la guardia de borrados masivos.

-
-

Privado por diseño

-

Sin telemetría, sin analítica, sin informes de fallos remotos. Las credenciales viven en GNOME Keyring a través de Secret Service.

+
+ +

Privado por diseño

Sin telemetría, sin analítica, sin informes de fallos remotos. Las credenciales viven en GNOME Keyring a través de Secret Service.

-
+ -
-

Lo que hace por ti

-
-

Multi-cuenta y multi-carpeta

Cada cuenta mantiene sus propias carpetas, y cada carpeta tiene su ruta remota (o su space de OpenCloud), su estado y sus disparadores.

-

Detección rápida de cambios

Vigilancia local recursiva con inotify y coalescencia de eventos. Los cambios remotos llegan por notify_push en Nextcloud o por intervalo.

-

Una sola cola por cuenta

Todos los disparadores confluyen en una cola que coalesce: un cambio local y uno remoto a la vez producen una sola pasada, nunca dos motores sobre la misma carpeta.

-

Guardia de borrados

Un borrado masivo local bloquea la sincronización antes de que el motor lo propague al servidor. Los CLI oficiales no piden confirmación en modo no interactivo; NextSync sí.

-

Vive en la bandeja

Abrir, registro y salir desde el icono de bandeja. Cerrar la ventana deja la app trabajando en segundo plano.

-

Login seguro

Login Flow v2 por navegador en Nextcloud, app password en OpenCloud. Las credenciales se guardan en el llavero del sistema.

+
+
+ +

Lo que hace por ti

+

Una capa de escritorio cuidada sobre los motores oficiales. Ni más, ni menos.

+ Ver la interfaz + +
-
+
+
+ +

Multi-cuenta y multi-carpeta

+

Cada cuenta mantiene sus propias carpetas, y cada carpeta tiene su ruta remota (o su space de OpenCloud), su estado y sus disparadores.

+
+
+ +

Detección rápida de cambios

+

Vigilancia local recursiva con inotify y coalescencia de eventos. Los cambios remotos llegan por notify_push en Nextcloud o por intervalo.

+
+
+ +

Una sola cola por cuenta

+

Todos los disparadores confluyen en una cola que coalesce: un cambio local y uno remoto a la vez producen una sola pasada, nunca dos motores sobre la misma carpeta.

+
+
+ +

Guardia de borrados

+

Un borrado masivo local bloquea la sincronización antes de que el motor lo propague al servidor. Los CLI oficiales no piden confirmación en modo no interactivo; NextSync sí.

+
+
+ +

Vive en la bandeja

+

Abrir, registro y salir desde el icono de bandeja. Cerrar la ventana deja la app trabajando en segundo plano.

+
+
+ +

Login seguro

+

Login Flow v2 por navegador en Nextcloud, app password en OpenCloud. Las credenciales se guardan en el llavero del sistema.

+
+
+ -
-

Un vistazo a la interfaz

-

Capturas reales de la app, en los dos idiomas y los dos temas.

-
-
- Ventana principal de NextSync +
+
+
+
+

Un vistazo a la interfaz

+

Tres ventanas reales de la app, en los dos idiomas y los dos temas.

+
+
+
+ + + +
+
+
+ + +
+
+ + +
+
+
- - -
-

+ +
Ventana principal, tema claro1440 × 960 · WebP
-
+ -
-

El compañero GNOME que faltaba

-

Comparativa con los clientes de escritorio oficiales, contrastada el 20 de agosto de 2026 contra sus repos públicos.

-
- - - - - - - - - - - -
NextSyncNextcloud DesktopOpenCloud Desktop
Interfaz nativa GNOME (GTK 4 / Libadwaita)✓✗ (Qt)✗ (Qt)
Multi-proveedor en una sola app✓✗✗
Motor de sync = CLI oficial del servidor✓◐ (motor propio)◐ (motor propio)
Archivos virtuales bajo demanda✗ (espejo completo)✓✓
Guardia de borrado masivo✓◐ (opt-in, solo GUI)◐ (opt-in, solo GUI)
Credenciales en el llavero del sistema✓✓✓
- -
- -
-

Instalación fácil y rápida

-

En Arch, CachyOS y derivadas hay paquete listo en cada release.

-
-

Instalación

-

NextSync es una aplicación de escritorio para Linux, pensada para GNOME. No hay versión para Android ni iOS: la instalación se hace en el ordenador.

-
-
-
sudo pacman -U nextsync-0.2.32-1-x86_64.pkg.tar.zst
- -

Descarga el paquete .pkg.tar.zst más reciente desde GitHub Releases y ajusta el nombre del fichero.

-
-

Requisitos

Linux con GNOME (o un escritorio con bandeja StatusNotifier). Para cuentas Nextcloud hace falta nextcloudcmd (paquete nextcloud-client en Arch); para OpenCloud, el CLI oficial opencloudcmd.

-

Desde el código fuente

El repo se compila con Cargo: cargo build --release y la batería se comprueba con cargo test. También hay PKGBUILD para generar el paquete localmente.

+
+ +
+
+
+ +

Instalación fácil y rápida

+

En Arch, CachyOS y derivadas hay paquete listo en cada release.

+
+

Instalación

+

NextSync es una aplicación de escritorio para Linux, pensada para GNOME. No hay versión para Android ni iOS: la instalación se hace en el ordenador.

+
+ + + Descargar release + + +
+
+
+ + pacman / x86_64 +
+
+ sudo pacman -U nextsync-0.2.32-1-x86_64.pkg.tar.zst + +
+

Descarga el paquete .pkg.tar.zst más reciente desde GitHub Releases y ajusta el nombre del fichero.

+
+
+

Requisitos

Linux con GNOME (o un escritorio con bandeja StatusNotifier). Para cuentas Nextcloud hace falta nextcloudcmd (paquete nextcloud-client en Arch); para OpenCloud, el CLI oficial opencloudcmd.

+

Desde el código fuente

El repo se compila con Cargo: cargo build --release y la batería se comprueba con cargo test. También hay PKGBUILD para generar el paquete localmente.

+
-
+ -
-

Por qué existe

-
+
+
+ +

Por qué existe

NextSync nació de una necesidad concreta: mantener espejos locales completos de varias cuentas Nextcloud y OpenCloud en el escritorio, sin depender de un cliente que solo hable con un servidor. Está escrito en Rust: un binario único, arranque instantáneo y una arquitectura que no ata el proyecto a un proveedor.

Es un proyecto personal, GPL-3.0+, gratuito siempre. Corre 24/7 en producción propia y cada versión se prueba ahí primero. Con colaboraciones o apoyo quizás podría crecer más rápido, pero no puedo prometer nada.

+ Explorar el código + +
-
-
¿Es un producto oficial de Nextcloud?

No. NextSync es un proyecto independiente y no oficial, sin afiliación con Nextcloud GmbH ni con el Heinlein Group. Nextcloud es una marca registrada de Nextcloud GmbH.

-
¿Soporta archivos virtuales?

No. NextSync mantiene espejos locales completos de las carpetas que elijas; no trabaja con ficheros bajo demanda.

-
¿Qué servidores soporta?

Nextcloud y OpenCloud hoy, a través de sus CLI oficiales de sincronización. Cualquier plataforma que publique un CLI así puede añadirse detrás de la misma abstracción.

-
¿Envía telemetría?

No. Sin telemetría, sin analítica y sin informes de fallos remotos.

+
+
¿Es un producto oficial de Nextcloud?

No. NextSync es un proyecto independiente y no oficial, sin afiliación con Nextcloud GmbH ni con el Heinlein Group. Nextcloud es una marca registrada de Nextcloud GmbH.

+
¿Soporta archivos virtuales?

No. NextSync mantiene espejos locales completos de las carpetas que elijas; no trabaja con ficheros bajo demanda.

+
¿Qué servidores soporta?

Nextcloud y OpenCloud hoy, a través de sus CLI oficiales de sincronización. Cualquier plataforma que publique un CLI así puede añadirse detrás de la misma abstracción.

+
¿Envía telemetría?

No. Sin telemetría, sin analítica y sin informes de fallos remotos.

-
+
-