diff --git a/AGENTS.md b/AGENTS.md index 2ae4e209..2240137f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -242,6 +242,14 @@ unsubscribe/confirm keep purpose tokens and never accept the session. - Banner: `#sessionBanner` ("Not you?" → `/session/logout`) - Characterization: `node --test worker/test/session_pins.test.mjs test/session_pins_client.test.mjs` +## Microsoft Clarity (optional heatmaps) + +Dormant until a project id is set. Loader: `site/clarity.js` (all public pages). +Config: `window.CROL_CLARITY_PROJECT_ID`, meta `crol-clarity-project-id`, or +`CONFIGURED_PROJECT_ID` in that file — leave empty to keep off. Skips on DNT/GPC; +masks form inputs; operator must set dashboard Masking mode to **Strict**. +Characterization: `node --test test/clarity.test.mjs`. Privacy copy: About → Privacy. + ## Maintaining this file Keep this file for knowledge useful to almost every future agent session in this project. diff --git a/SECURITY.md b/SECURITY.md index 6ab458da..5d0b1ab3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -32,7 +32,9 @@ prefer otherwise. - **Admin routes** are bearer-key-gated and 404 when unconfigured; their obscurity is not a defense. - **The static site** is dependency-free vanilla JS on GitHub Pages: no build pipeline to - poison, no third-party scripts beyond Cloudflare's cookieless analytics beacon. + poison. Third-party scripts are limited to Cloudflare's cookieless analytics beacon and, + when a project id is configured, Microsoft Clarity (masked heatmaps; skipped on + Do Not Track / Global Privacy Control — see `site/clarity.js`). - **The notices mirror (D1)** holds only already-public City Record data plus the raw source rows; nothing personal ever enters it. **Queues** carry subscription *keys*, not addresses. diff --git a/site/about.html b/site/about.html index 11129f70..638f613b 100644 --- a/site/about.html +++ b/site/about.html @@ -23,6 +23,7 @@ + @@ -200,7 +201,7 @@

Send feedback

- +
@@ -211,13 +212,7 @@

Send feedback

Privacy

CityScroll has no password accounts and no ad tech. It does not track you across other sites.

- +

About our content

An AI assistant (Claude) drafts this site's copy — headings, explanations, pages like this one. A human editor checks it before it goes live. The data is not AI-generated. Every notice, dollar figure, and date comes straight from NYC Open Data (opens in new tab), unedited.

diff --git a/site/api.html b/site/api.html index 13f74b18..7f120f53 100644 --- a/site/api.html +++ b/site/api.html @@ -23,6 +23,7 @@ + diff --git a/site/changelog.html b/site/changelog.html index bfd9d69f..3ccc9187 100644 --- a/site/changelog.html +++ b/site/changelog.html @@ -23,6 +23,7 @@ + diff --git a/site/clarity.js b/site/clarity.js new file mode 100644 index 00000000..5f1daac5 --- /dev/null +++ b/site/clarity.js @@ -0,0 +1,133 @@ +// Microsoft Clarity loader — heatmaps and session interaction patterns. +// Dormant until a project id is configured. Never blocks page function if blocked/down. +// +// Config (either works; empty keeps Clarity off): +// 1. window.CROL_CLARITY_PROJECT_ID = "" (set before this script) +// 2. +// 3. Edit CONFIGURED_PROJECT_ID below after creating a project at clarity.microsoft.com +// +// Privacy posture: +// - Skip entirely when the browser signals Do Not Track or Global Privacy Control +// - Mask all form inputs/textareas/selects before load (email fields never plain-text) +// - Operator must set project Masking mode to Strict in the Clarity dashboard +// - Fail-soft: missing id, network errors, and ad-blockers leave the site fully usable +(function (root) { + "use strict"; + + // Operator: paste the Clarity project id between the quotes to enable. Leave empty for dormant. + const CONFIGURED_PROJECT_ID = ""; + + const TAG_ORIGIN = "https://www.clarity.ms/tag/"; + const META_NAME = "crol-clarity-project-id"; + + function resolveProjectId(win, doc) { + const fromWindow = win && typeof win.CROL_CLARITY_PROJECT_ID === "string" + ? win.CROL_CLARITY_PROJECT_ID.trim() + : ""; + if (fromWindow) return fromWindow; + const configured = String(CONFIGURED_PROJECT_ID || "").trim(); + if (configured) return configured; + try { + const meta = doc && doc.querySelector && doc.querySelector(`meta[name="${META_NAME}"]`); + return meta && meta.content ? String(meta.content).trim() : ""; + } catch { + return ""; + } + } + + function prefersNoTracking(nav) { + if (!nav) return false; + const dnt = nav.doNotTrack; + if (dnt === "1" || dnt === "yes") return true; + if (nav.msDoNotTrack === "1") return true; + // Global Privacy Control (Sec-GPC / navigator.globalPrivacyControl) + if (nav.globalPrivacyControl === true) return true; + return false; + } + + function shouldLoad({ projectId, navigator: nav }) { + if (!projectId) return false; + if (prefersNoTracking(nav)) return false; + return true; + } + + function applyInputMasking(doc) { + if (!doc || !doc.querySelectorAll) return 0; + let count = 0; + try { + const nodes = doc.querySelectorAll("input, textarea, select"); + for (const el of nodes) { + if (el.getAttribute("data-clarity-mask") === "true") continue; + el.setAttribute("data-clarity-mask", "true"); + count += 1; + } + // Belt-and-suspenders on known email capture fields + for (const id of ["adest", "fbemail"]) { + const el = doc.getElementById(id); + if (el) el.setAttribute("data-clarity-mask", "true"); + } + } catch { + // Masking is best-effort; never break the page. + } + return count; + } + + function injectClarityScript(doc, projectId) { + if (!doc || !doc.createElement) return null; + // Official Clarity bootstrap: queue calls until the tag arrives. + const w = root; + w.clarity = w.clarity || function () { + (w.clarity.q = w.clarity.q || []).push(arguments); + }; + const script = doc.createElement("script"); + script.async = true; + script.src = TAG_ORIGIN + encodeURIComponent(projectId); + script.setAttribute("data-crol-clarity", "1"); + // Failure-tolerant: onerror is a no-op; ad-blockers fail silently. + script.onerror = function () { /* site remains fully functional */ }; + const anchor = doc.head || doc.documentElement || doc.body; + if (!anchor) return null; + anchor.appendChild(script); + return script; + } + + function boot(options) { + const win = (options && options.window) || root; + const doc = (options && options.document) || (win && win.document); + const nav = (options && options.navigator) || (win && win.navigator); + const projectId = resolveProjectId(win, doc); + if (!shouldLoad({ projectId, navigator: nav })) { + return { loaded: false, projectId: projectId || "", reason: !projectId ? "unconfigured" : "opt-out" }; + } + applyInputMasking(doc); + try { + injectClarityScript(doc, projectId); + return { loaded: true, projectId, reason: "ok" }; + } catch { + return { loaded: false, projectId, reason: "error" }; + } + } + + const api = Object.freeze({ + CONFIGURED_PROJECT_ID, + META_NAME, + TAG_ORIGIN, + resolveProjectId, + prefersNoTracking, + shouldLoad, + applyInputMasking, + injectClarityScript, + boot, + }); + + root.CROLClarity = api; + + // Auto-boot in the browser after the document is interactive enough for querySelector. + if (typeof document !== "undefined") { + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", function () { boot(); }, { once: true }); + } else { + boot(); + } + } +})(typeof window !== "undefined" ? window : globalThis); diff --git a/site/data.html b/site/data.html index 8e3a5597..6af8d3ec 100644 --- a/site/data.html +++ b/site/data.html @@ -23,6 +23,7 @@ + diff --git a/site/i18n.js b/site/i18n.js index 92733e93..e7d5a211 100644 --- a/site/i18n.js +++ b/site/i18n.js @@ -1279,7 +1279,7 @@ const STRINGS = { about_foot_html: "CityScroll · a search interface over The City Record (opens in new tab) · Changelog · Stats · Home", about_h_privacy: "Privacy", about_p_privacy_intro: "CityScroll has no password accounts and no ad tech. It does not track you across other sites.", - about_li_privacy_html: "
  • Searches and filters use NYC Open Data.
  • The \"Ask\" box lets you search in plain English. Your text is sent to Anthropic's Claude, which turns it into filters. We do not save your text.
  • Subscribing or sending feedback saves what you send us. This includes your alert or message and your email, if you share one. We also keep some basic info about your request, like your IP address and browser. We keep this for a short time to stop spam and abuse. Every alert email has a one-click unsubscribe link.
  • Email links and pins — clicking a link from your alert email signs you in on that device so your pinned items can follow you. A short banner says you are signed in and lets you sign out (\"Not you?\"). Changing or canceling a subscription still needs its own email link.
  • Page views are tracked with Cloudflare Web Analytics. It uses no cookies and only shows totals. It counts visits. It does not know who you are or follow you to other sites.
  • ", + about_li_privacy_html: "
  • Searches and filters use NYC Open Data.
  • The \"Ask\" box lets you search in plain English. Your text is sent to Anthropic's Claude, which turns it into filters. We do not save your text.
  • Subscribing or sending feedback saves what you send us. This includes your alert or message and your email, if you share one. We also keep some basic info about your request, like your IP address and browser. We keep this for a short time to stop spam and abuse. Every alert email has a one-click unsubscribe link.
  • Email links and pins — clicking a link from your alert email signs you in on that device so your pinned items can follow you. A short banner says you are signed in and lets you sign out (\"Not you?\"). Changing or canceling a subscription still needs its own email link.
  • Page views are tracked with Cloudflare Web Analytics. It uses no cookies and only shows totals. It counts visits. It does not know who you are or follow you to other sites.
  • Click and scroll maps (Microsoft Clarity, when turned on) show where people tap or scroll so we can fix hard-to-use spots. Typed text and form fields — including email — are masked and never sent as plain text. Clarity does not load if your browser signals Do Not Track or Global Privacy Control. It is not used for ads or personal profiles.
  • ", session_signed_in: "Signed in via your email link — your pins can follow you on this device.", session_not_you: "Not you?", session_dismiss: "Dismiss", diff --git a/site/i18n/lang/ar.js b/site/i18n/lang/ar.js index 2cf7788b..beca6a82 100644 --- a/site/i18n/lang/ar.js +++ b/site/i18n/lang/ar.js @@ -916,7 +916,7 @@ about_foot_html: "CityScroll · واجهة بحث فوق The City Record (يفتح في علامة تبويب جديدة) · سجل التغييرات · إحصاءات · الرئيسية", about_h_privacy: "الخصوصية", about_p_privacy_intro: "لا يملك CityScroll حسابات بكلمات مرور ولا تقنيات إعلانية. ولا يتتبعك عبر مواقع أخرى.", - about_li_privacy_html: "
  • عمليات البحث والتصفية تستخدم NYC Open Data.
  • مربع \"اسأل\" يتيح لك البحث بلغة إنجليزية واضحة. يُرسَل نصك إلى Claude من Anthropic، الذي يحوّله إلى عوامل تصفية. نحن لا نحفظ نصك.
  • الاشتراك أو إرسال الملاحظات يحفظ ما ترسله لنا. يشمل هذا تنبيهك أو رسالتك وبريدك الإلكتروني، إن شاركته. كما نحتفظ ببعض المعلومات الأساسية عن طلبك، مثل عنوان IP الخاص بك والمتصفح. نحتفظ بهذا لفترة قصيرة لمنع البريد المزعج وسوء الاستخدام. يحمل كل بريد تنبيه رابط إلغاء اشتراك بنقرة واحدة.
  • روابط البريد والعناصر المثبتة — النقر على رابط من بريد التنبيه يسجّل دخولك على ذلك الجهاز حتى تستطيع عناصرك المثبتة متابعتك. شريط قصير يوضح أنك مسجّل الدخول ويتيح الخروج (\"لست أنت؟\"). تغيير الاشتراك أو إلغاؤه ما زال يحتاج رابط بريد خاصًا به.
  • مشاهدات الصفحة تُتَبَّع باستخدام Cloudflare Web Analytics. لا يستخدم ملفات تعريف ارتباط ويعرض فقط الإجماليات. يعدّ الزيارات. لا يعرف من أنت ولا يتبعك إلى مواقع أخرى.
  • ", + about_li_privacy_html: "
  • عمليات البحث والتصفية تستخدم NYC Open Data.
  • مربع \"اسأل\" يتيح لك البحث بلغة إنجليزية واضحة. يُرسَل نصك إلى Claude من Anthropic، الذي يحوّله إلى عوامل تصفية. نحن لا نحفظ نصك.
  • الاشتراك أو إرسال الملاحظات يحفظ ما ترسله لنا. يشمل هذا تنبيهك أو رسالتك وبريدك الإلكتروني، إن شاركته. كما نحتفظ ببعض المعلومات الأساسية عن طلبك، مثل عنوان IP الخاص بك والمتصفح. نحتفظ بهذا لفترة قصيرة لمنع البريد المزعج وسوء الاستخدام. يحمل كل بريد تنبيه رابط إلغاء اشتراك بنقرة واحدة.
  • روابط البريد والعناصر المثبتة — النقر على رابط من بريد التنبيه يسجّل دخولك على ذلك الجهاز حتى تستطيع عناصرك المثبتة متابعتك. شريط قصير يوضح أنك مسجّل الدخول ويتيح الخروج (\"لست أنت؟\"). تغيير الاشتراك أو إلغاؤه ما زال يحتاج رابط بريد خاصًا به.
  • مشاهدات الصفحة تُتَبَّع باستخدام Cloudflare Web Analytics. لا يستخدم ملفات تعريف ارتباط ويعرض فقط الإجماليات. يعدّ الزيارات. لا يعرف من أنت ولا يتبعك إلى مواقع أخرى.
  • خرائط النقر والتمرير (Microsoft Clarity عند تفعيله) تُظهر أين يضغط الناس أو يمرّرون حتى نصلح الأجزاء الصعبة الاستخدام. النص المكتوب وحقول النماذج — بما فيها البريد — تُخفى ولا تُرسل كنص واضح أبدًا. لا يُحمَّل Clarity إذا أشار متصفحك إلى عدم التتبع أو Global Privacy Control. لا يُستخدم للإعلانات أو الملفات الشخصية.
  • ", session_signed_in: "تم تسجيل الدخول عبر رابط بريدك — يمكن لعناصرك المثبتة متابعتك على هذا الجهاز.", session_not_you: "لست أنت؟", session_dismiss: "إغلاق", diff --git a/site/i18n/lang/bn.js b/site/i18n/lang/bn.js index a572c9cf..c4ebebeb 100644 --- a/site/i18n/lang/bn.js +++ b/site/i18n/lang/bn.js @@ -903,7 +903,7 @@ about_foot_html: "CityScroll · The City Record (নতুন ট্যাবে খোলে)-এর উপর একটি অনুসন্ধান ইন্টারফেস · পরিবর্তন লগ · পরিসংখ্যান · হোম", about_h_privacy: "গোপনীয়তা", about_p_privacy_intro: "CityScroll-এ কোনো পাসওয়ার্ড অ্যাকাউন্ট নেই এবং কোনো বিজ্ঞাপন প্রযুক্তি নেই। এটি আপনাকে অন্য সাইটে ট্র্যাক করে না।", - about_li_privacy_html: "
  • অনুসন্ধান ও ফিল্টার NYC Open Data ব্যবহার করে।
  • \"জিজ্ঞাসা করুন\" বাক্সটি আপনাকে সহজ ইংরেজিতে অনুসন্ধান করতে দেয়। এটি আপনার লেখা আমাদের ওয়ার্কারে পাঠায়। ওয়ার্কার তারপর এটি Anthropic-এর Claude-এ পাঠায়, যা আপনার শব্দগুলিকে ফিল্টারে রূপান্তরিত করে। আমরা আপনার লেখা সংরক্ষণ করি না।
  • সাবস্ক্রাইব করা বা মতামত পাঠানো আপনি আমাদের যা পাঠান তা সংরক্ষণ করে। এর মধ্যে আপনার সতর্কতা বা বার্তা এবং আপনার ইমেইল, যদি আপনি একটি শেয়ার করেন, অন্তর্ভুক্ত। আমরা আপনার অনুরোধ সম্পর্কে কিছু মৌলিক তথ্যও রাখি, যেমন আপনার আইপি ঠিকানা এবং ব্রাউজার। স্প্যাম ও অপব্যবহার বন্ধ করতে আমরা এটি অল্প সময়ের জন্য রাখি। প্রতিটি সতর্কতা ইমেইলে এক-ক্লিকে সাবস্ক্রিপশন বাতিলের লিংক থাকে।
  • ইমেইল লিংক ও পিন — আপনার সতর্কতা ইমেইলের লিংকে ক্লিক করলে সেই ডিভাইসে আপনি সাইন ইন হন, যাতে আপনার পিন করা আইটেমগুলি আপনার সাথে থাকতে পারে। একটি সংক্ষিপ্ত ব্যানার বলে আপনি সাইন ইন আছেন এবং সাইন আউট করতে দেয় (\"আপনি নন?\")। সাবস্ক্রিপশন পরিবর্তন বা বাতিল করতে এখনও নিজস্ব ইমেইল লিংক লাগে।
  • পৃষ্ঠা দর্শন Cloudflare Web Analytics দিয়ে ট্র্যাক করা হয়। এটি কোনো কুকি ব্যবহার করে না এবং শুধু সমষ্টি দেখায়। এটি দর্শন গণনা করে। এটি জানে না আপনি কে বা আপনাকে অন্য সাইটে অনুসরণ করে না।
  • ", + about_li_privacy_html: "
  • অনুসন্ধান ও ফিল্টার NYC Open Data ব্যবহার করে।
  • \"জিজ্ঞাসা করুন\" বাক্সটি আপনাকে সহজ ইংরেজিতে অনুসন্ধান করতে দেয়। এটি আপনার লেখা আমাদের ওয়ার্কারে পাঠায়। ওয়ার্কার তারপর এটি Anthropic-এর Claude-এ পাঠায়, যা আপনার শব্দগুলিকে ফিল্টারে রূপান্তরিত করে। আমরা আপনার লেখা সংরক্ষণ করি না।
  • সাবস্ক্রাইব করা বা মতামত পাঠানো আপনি আমাদের যা পাঠান তা সংরক্ষণ করে। এর মধ্যে আপনার সতর্কতা বা বার্তা এবং আপনার ইমেইল, যদি আপনি একটি শেয়ার করেন, অন্তর্ভুক্ত। আমরা আপনার অনুরোধ সম্পর্কে কিছু মৌলিক তথ্যও রাখি, যেমন আপনার আইপি ঠিকানা এবং ব্রাউজার। স্প্যাম ও অপব্যবহার বন্ধ করতে আমরা এটি অল্প সময়ের জন্য রাখি। প্রতিটি সতর্কতা ইমেইলে এক-ক্লিকে সাবস্ক্রিপশন বাতিলের লিংক থাকে।
  • ইমেইল লিংক ও পিন — আপনার সতর্কতা ইমেইলের লিংকে ক্লিক করলে সেই ডিভাইসে আপনি সাইন ইন হন, যাতে আপনার পিন করা আইটেমগুলি আপনার সাথে থাকতে পারে। একটি সংক্ষিপ্ত ব্যানার বলে আপনি সাইন ইন আছেন এবং সাইন আউট করতে দেয় (\"আপনি নন?\")। সাবস্ক্রিপশন পরিবর্তন বা বাতিল করতে এখনও নিজস্ব ইমেইল লিংক লাগে।
  • পৃষ্ঠা দর্শন Cloudflare Web Analytics দিয়ে ট্র্যাক করা হয়। এটি কোনো কুকি ব্যবহার করে না এবং শুধু সমষ্টি দেখায়। এটি দর্শন গণনা করে। এটি জানে না আপনি কে বা আপনাকে অন্য সাইটে অনুসরণ করে না।
  • ক্লিক ও স্ক্রল মানচিত্র (Microsoft Clarity, চালু থাকলে) দেখায় মানুষ কোথায় ট্যাপ বা স্ক্রল করে, যাতে আমরা কঠিন অংশ ঠিক করতে পারি। টাইপ করা লেখা ও ফর্ম ক্ষেত্র — ইমেইলসহ — মাস্ক করা হয় এবং সাধারণ লেখা হিসেবে কখনো পাঠানো হয় না। আপনার ব্রাউজার Do Not Track বা Global Privacy Control সংকেত দিলে Clarity লোড হয় না। এটি বিজ্ঞাপন বা ব্যক্তিগত প্রোফাইলের জন্য ব্যবহৃত হয় না।
  • ", session_signed_in: "ইমেইল লিংক দিয়ে সাইন ইন — আপনার পিন এই ডিভাইসে আপনার সাথে থাকতে পারে।", session_not_you: "আপনি নন?", session_dismiss: "বন্ধ", diff --git a/site/i18n/lang/es.js b/site/i18n/lang/es.js index 2e2ef946..d120ef87 100644 --- a/site/i18n/lang/es.js +++ b/site/i18n/lang/es.js @@ -903,7 +903,7 @@ about_foot_html: "CityScroll · una interfaz de búsqueda sobre The City Record (se abre en una pestaña nueva) · Registro de cambios · Estadísticas · Inicio", about_h_privacy: "Privacidad", about_p_privacy_intro: "CityScroll no tiene cuentas con contraseña ni tecnología publicitaria. No lo rastrea en otros sitios.", - about_li_privacy_html: "
  • Las búsquedas y filtros usan NYC Open Data.
  • El cuadro \"Preguntar\" le permite buscar en lenguaje llano. Su texto se envía a Claude de Anthropic, que lo convierte en filtros. No guardamos su texto.
  • Suscribirse o enviar comentarios guarda lo que usted nos envía. Esto incluye su alerta o mensaje, y su correo, si comparte uno. También guardamos información básica sobre su solicitud, como su dirección IP y navegador. Guardamos esto por poco tiempo para bloquear spam y abuso. Cada correo de alerta tiene un enlace de cancelación con un clic.
  • Enlaces del correo y fijados — al hacer clic en un enlace de su correo de alerta, se inicia sesión en ese dispositivo para que sus elementos fijados puedan seguirle. Un aviso breve dice que está conectado y le permite salir (\"¿No es usted?\"). Cambiar o cancelar una suscripción sigue necesitando su propio enlace del correo.
  • Las visitas a la página se rastrean con Cloudflare Web Analytics. No usa cookies y solo muestra totales. Cuenta visitas. No sabe quién es usted ni lo sigue a otros sitios.
  • ", + about_li_privacy_html: "
  • Las búsquedas y filtros usan NYC Open Data.
  • El cuadro \"Preguntar\" le permite buscar en lenguaje llano. Su texto se envía a Claude de Anthropic, que lo convierte en filtros. No guardamos su texto.
  • Suscribirse o enviar comentarios guarda lo que usted nos envía. Esto incluye su alerta o mensaje, y su correo, si comparte uno. También guardamos información básica sobre su solicitud, como su dirección IP y navegador. Guardamos esto por poco tiempo para bloquear spam y abuso. Cada correo de alerta tiene un enlace de cancelación con un clic.
  • Enlaces del correo y fijados — al hacer clic en un enlace de su correo de alerta, se inicia sesión en ese dispositivo para que sus elementos fijados puedan seguirle. Un aviso breve dice que está conectado y le permite salir (\"¿No es usted?\"). Cambiar o cancelar una suscripción sigue necesitando su propio enlace del correo.
  • Las visitas a la página se rastrean con Cloudflare Web Analytics. No usa cookies y solo muestra totales. Cuenta visitas. No sabe quién es usted ni lo sigue a otros sitios.
  • Mapas de clics y desplazamiento (Microsoft Clarity, cuando está activo) muestran dónde la gente toca o se desplaza para que podamos corregir partes difíciles de usar. El texto escrito y los campos de formulario —incluido el correo— se ocultan y nunca se envían en texto claro. Clarity no se carga si su navegador indica No Rastrear o Global Privacy Control. No se usa para anuncios ni perfiles personales.
  • ", session_signed_in: "Sesión iniciada con el enlace de su correo — sus fijados pueden seguirle en este dispositivo.", session_not_you: "¿No es usted?", session_dismiss: "Cerrar", diff --git a/site/i18n/lang/fr.js b/site/i18n/lang/fr.js index bcb4c7c6..87418dc9 100644 --- a/site/i18n/lang/fr.js +++ b/site/i18n/lang/fr.js @@ -1011,7 +1011,7 @@ about_foot_html: "CityScroll · une interface de recherche pour The City Record (s'ouvre dans un nouvel onglet) · Journal des modifications · Statistiques · Accueil", about_h_privacy: "Confidentialité", about_p_privacy_intro: "CityScroll n'a ni comptes avec mot de passe ni technologie publicitaire. Il ne vous suit pas sur d'autres sites.", - about_li_privacy_html: "
  • Les recherches et filtres utilisent NYC Open Data.
  • La zone « Demander » vous permet de rechercher en langage clair. Elle envoie votre texte à notre serveur (worker). Ce dernier l'envoie ensuite à Claude d'Anthropic, qui transforme vos mots en filtres. Nous ne conservons pas votre texte.
  • S'abonner ou envoyer des commentaires enregistre ce que vous nous envoyez. Cela inclut votre alerte ou votre message, ainsi que votre courriel, si vous en fournissez un. Nous conservons aussi des informations de base sur votre demande, comme votre adresse IP et votre navigateur. Nous les conservons un court moment pour bloquer les pourriels et les abus. Chaque courriel d'alerte comporte un lien de désabonnement en un clic.
  • Liens des courriels et épingles — cliquer un lien de votre courriel d'alerte vous connecte sur cet appareil pour que vos éléments épinglés puissent vous suivre. Une courte bannière indique que vous êtes connecté et permet de vous déconnecter (« Ce n'est pas vous ? »). Modifier ou annuler un abonnement exige encore son propre lien de courriel.
  • Les pages consultées sont suivies avec Cloudflare Web Analytics. Ce service n'utilise aucun témoin et n'affiche que des totaux. Il compte les visites. Il ne sait pas qui vous êtes et ne vous suit pas vers d'autres sites.
  • ", + about_li_privacy_html: "
  • Les recherches et filtres utilisent NYC Open Data.
  • La zone « Demander » vous permet de rechercher en langage clair. Elle envoie votre texte à notre serveur (worker). Ce dernier l'envoie ensuite à Claude d'Anthropic, qui transforme vos mots en filtres. Nous ne conservons pas votre texte.
  • S'abonner ou envoyer des commentaires enregistre ce que vous nous envoyez. Cela inclut votre alerte ou votre message, ainsi que votre courriel, si vous en fournissez un. Nous conservons aussi des informations de base sur votre demande, comme votre adresse IP et votre navigateur. Nous les conservons un court moment pour bloquer les pourriels et les abus. Chaque courriel d'alerte comporte un lien de désabonnement en un clic.
  • Liens des courriels et épingles — cliquer un lien de votre courriel d'alerte vous connecte sur cet appareil pour que vos éléments épinglés puissent vous suivre. Une courte bannière indique que vous êtes connecté et permet de vous déconnecter (« Ce n'est pas vous ? »). Modifier ou annuler un abonnement exige encore son propre lien de courriel.
  • Les pages consultées sont suivies avec Cloudflare Web Analytics. Ce service n'utilise aucun témoin et n'affiche que des totaux. Il compte les visites. Il ne sait pas qui vous êtes et ne vous suit pas vers d'autres sites.
  • Cartes de clics et de défilement (Microsoft Clarity, lorsqu’il est activé) montrent où les gens appuient ou font défiler pour que nous puissions corriger les parties difficiles à utiliser. Le texte saisi et les champs de formulaire — y compris le courriel — sont masqués et ne sont jamais envoyés en clair. Clarity ne se charge pas si votre navigateur signale Do Not Track ou Global Privacy Control. Il ne sert ni à la publicité ni aux profils personnels.
  • ", session_signed_in: "Connecté via le lien de votre courriel — vos épingles peuvent vous suivre sur cet appareil.", session_not_you: "Ce n'est pas vous ?", session_dismiss: "Fermer", diff --git a/site/i18n/lang/ht.js b/site/i18n/lang/ht.js index 8b638cc7..c164eccd 100644 --- a/site/i18n/lang/ht.js +++ b/site/i18n/lang/ht.js @@ -902,7 +902,7 @@ about_foot_html: "CityScroll · yon entèfas rechèch sou The City Record (louvri nan yon nouvo fenèt) · Istorik chanjman · Estatistik · Akèy", about_h_privacy: "Konfidansyalite", about_p_privacy_intro: "CityScroll pa gen kont ak modpas e pa gen teknoloji piblisite. Li pa swiv ou sou lòt sit.", - about_li_privacy_html: "
  • Rechèch ak filtè sèvi ak NYC Open Data.
  • Bwat \"Mande\" a kite w chèche an angle klè. Li voye tèks ou nan worker nou an. Worker la voye l apre bay Claude, Anthropic, ki transfòme mo ou yo an filtè. Nou pa anrejistre tèks ou.
  • Abòne oswa voye kòmantè anrejistre sa ou voye ban nou an. Sa gen ladan l alèt ou oswa mesaj ou, ak imèl ou, si w pataje youn. Nou kenbe tou kèk enfòmasyon debaz sou demann ou, tankou adrès IP ak navigatè w. Nou kenbe sa pou yon ti tan pou bloke spam ak abi. Chak imèl alèt gen yon lyen pou dezabone nan yon sèl klik.
  • Lyen imèl ak epingle — lè w klike yon lyen nan imèl alèt ou a, sa konekte w sou aparèy sa a pou atik ou epingle yo ka swiv ou. Yon ti banyè di ou konekte e kite w dekonekte (\"Se pa ou?\"). Chanje oswa anile yon abònman toujou bezwen pwòp lyen imèl li.
  • Vizit paj yo swiv ak Cloudflare Web Analytics. Li pa sèvi ak cookies e li montre sèlman total yo. Li konte vizit. Li pa konnen ki moun ou ye ni li pa swiv ou nan lòt sit.
  • ", + about_li_privacy_html: "
  • Rechèch ak filtè sèvi ak NYC Open Data.
  • Bwat \"Mande\" a kite w chèche an angle klè. Li voye tèks ou nan worker nou an. Worker la voye l apre bay Claude, Anthropic, ki transfòme mo ou yo an filtè. Nou pa anrejistre tèks ou.
  • Abòne oswa voye kòmantè anrejistre sa ou voye ban nou an. Sa gen ladan l alèt ou oswa mesaj ou, ak imèl ou, si w pataje youn. Nou kenbe tou kèk enfòmasyon debaz sou demann ou, tankou adrès IP ak navigatè w. Nou kenbe sa pou yon ti tan pou bloke spam ak abi. Chak imèl alèt gen yon lyen pou dezabone nan yon sèl klik.
  • Lyen imèl ak epingle — lè w klike yon lyen nan imèl alèt ou a, sa konekte w sou aparèy sa a pou atik ou epingle yo ka swiv ou. Yon ti banyè di ou konekte e kite w dekonekte (\"Se pa ou?\"). Chanje oswa anile yon abònman toujou bezwen pwòp lyen imèl li.
  • Vizit paj yo swiv ak Cloudflare Web Analytics. Li pa sèvi ak cookies e li montre sèlman total yo. Li konte vizit. Li pa konnen ki moun ou ye ni li pa swiv ou nan lòt sit.
  • Kat klike ak woule (Microsoft Clarity, lè li aktive) montre kote moun tape oswa woule pou nou ka korije pati ki difisil pou itilize. Tèks ou tape ak chan fòm yo — enkli imèl — yo kache epi yo pa janm voye an tèks klè. Clarity pa chaje si navigatè ou voye siyal Do Not Track oswa Global Privacy Control. Li pa sèvi pou piblisite ni pwofil pèsonèl.
  • ", session_signed_in: "Konekte grasa lyen imèl ou — epingle ou yo ka swiv ou sou aparèy sa a.", session_not_you: "Se pa ou?", session_dismiss: "Fèmen", diff --git a/site/i18n/lang/ko.js b/site/i18n/lang/ko.js index f13e5801..3fd12c49 100644 --- a/site/i18n/lang/ko.js +++ b/site/i18n/lang/ko.js @@ -907,7 +907,7 @@ about_foot_html: "CityScroll · The City Record (새 탭에서 열림) 검색 인터페이스 · 변경 이력 · 통계 · ", about_h_privacy: "개인정보 보호", about_p_privacy_intro: "CityScroll에는 비밀번호 계정이 없고 광고 기술도 없습니다. 다른 사이트에서 사용자를 추적하지 않습니다.", - about_li_privacy_html: "
  • 검색과 필터는 NYC Open Data를 사용합니다.
  • \"Ask\" 상자를 이용하면 쉬운 영어로 검색할 수 있습니다. 입력하신 텍스트는 저희 워커로 전송됩니다. 워커는 이를 Anthropic의 Claude로 전달하고, Claude가 여러분의 문장을 필터로 변환합니다. 저희는 텍스트를 저장하지 않습니다.
  • 구독 신청이나 피드백 전송은 보내신 내용을 저장합니다. 여기에는 알림 또는 메시지 내용과, 공유하신 경우 이메일 주소가 포함됩니다. 또한 IP 주소, 브라우저 등 요청에 대한 기본 정보도 일부 보관합니다. 스팸과 남용을 막기 위해 이를 짧은 기간 동안 보관합니다. 모든 알림 이메일에는 원클릭 구독 취소 링크가 있습니다.
  • 이메일 링크와 고정 항목 — 알림 이메일의 링크를 클릭하면 해당 기기에 로그인되어 고정한 항목이 따라올 수 있습니다. 짧은 배너가 로그인 상태임을 알리고 로그아웃할 수 있게 합니다(\"본인이 아닌가요?\"). 구독 변경·취소는 여전히 전용 이메일 링크가 필요합니다.
  • 페이지 조회수는 Cloudflare Web Analytics로 추적됩니다. 쿠키를 사용하지 않으며 총계만 보여줍니다. 방문 횟수를 집계할 뿐, 여러분이 누구인지 알지 못하며 다른 사이트까지 추적하지 않습니다.
  • ", + about_li_privacy_html: "
  • 검색과 필터는 NYC Open Data를 사용합니다.
  • \"Ask\" 상자를 이용하면 쉬운 영어로 검색할 수 있습니다. 입력하신 텍스트는 저희 워커로 전송됩니다. 워커는 이를 Anthropic의 Claude로 전달하고, Claude가 여러분의 문장을 필터로 변환합니다. 저희는 텍스트를 저장하지 않습니다.
  • 구독 신청이나 피드백 전송은 보내신 내용을 저장합니다. 여기에는 알림 또는 메시지 내용과, 공유하신 경우 이메일 주소가 포함됩니다. 또한 IP 주소, 브라우저 등 요청에 대한 기본 정보도 일부 보관합니다. 스팸과 남용을 막기 위해 이를 짧은 기간 동안 보관합니다. 모든 알림 이메일에는 원클릭 구독 취소 링크가 있습니다.
  • 이메일 링크와 고정 항목 — 알림 이메일의 링크를 클릭하면 해당 기기에 로그인되어 고정한 항목이 따라올 수 있습니다. 짧은 배너가 로그인 상태임을 알리고 로그아웃할 수 있게 합니다(\"본인이 아닌가요?\"). 구독 변경·취소는 여전히 전용 이메일 링크가 필요합니다.
  • 페이지 조회수는 Cloudflare Web Analytics로 추적됩니다. 쿠키를 사용하지 않으며 총계만 보여줍니다. 방문 횟수를 집계할 뿐, 여러분이 누구인지 알지 못하며 다른 사이트까지 추적하지 않습니다.
  • 클릭·스크롤 지도(Microsoft Clarity, 켜져 있을 때)는 사람들이 탭하거나 스크롤하는 위치를 보여 쓰기 어려운 부분을 고칠 수 있게 합니다. 입력 텍스트와 양식 필드(이메일 포함)는 가려지며 평문으로 전송되지 않습니다. 브라우저가 Do Not Track 또는 Global Privacy Control을 알리면 Clarity를 로드하지 않습니다. 광고나 개인 프로필에는 쓰이지 않습니다.
  • ", session_signed_in: "이메일 링크로 로그인됨 — 이 기기에서 고정 항목이 따라올 수 있습니다.", session_not_you: "본인이 아닌가요?", session_dismiss: "닫기", diff --git a/site/i18n/lang/pl.js b/site/i18n/lang/pl.js index ae9a719a..879219c1 100644 --- a/site/i18n/lang/pl.js +++ b/site/i18n/lang/pl.js @@ -923,7 +923,7 @@ about_foot_html: "CityScroll · interfejs wyszukiwania dla The City Record (otwiera się w nowej karcie) · Historia zmian · Statystyki · Strona główna", about_h_privacy: "Prywatność", about_p_privacy_intro: "CityScroll nie ma kont z hasłem ani technologii reklamowych. Nie śledzi Cię na innych stronach.", - about_li_privacy_html: "
  • Wyszukiwania i filtry korzystają z NYC Open Data.
  • Pole „Zapytaj” pozwala szukać prostym językiem. Wysyła Twój tekst do naszego workera. Worker przesyła go następnie do Claude firmy Anthropic, który zamienia Twoje słowa na filtry. Nie zapisujemy Twojego tekstu.
  • Subskrybowanie lub wysyłanie opinii zapisuje to, co nam przesyłasz. Obejmuje to Twoje powiadomienie lub wiadomość oraz Twój e-mail, jeśli go podasz. Przechowujemy też podstawowe informacje o Twoim żądaniu, takie jak adres IP i przeglądarka. Przechowujemy to krótko, aby blokować spam i nadużycia. Każdy e-mail z powiadomieniem zawiera link do wypisania się jednym kliknięciem.
  • Linki z e-maili i pinezki — kliknięcie linku z e-maila z powiadomieniem loguje Cię na tym urządzeniu, aby przypięte elementy mogły za Tobą podążać. Krótki baner informuje, że jesteś zalogowany, i pozwala się wylogować („To nie Ty?”). Zmiana lub anulowanie subskrypcji nadal wymaga własnego linku z e-maila.
  • Odsłony stron są śledzone za pomocą Cloudflare Web Analytics. Nie używa plików cookie i pokazuje tylko sumy. Liczy wizyty. Nie wie, kim jesteś, ani nie śledzi Cię na innych stronach.
  • ", + about_li_privacy_html: "
  • Wyszukiwania i filtry korzystają z NYC Open Data.
  • Pole „Zapytaj” pozwala szukać prostym językiem. Wysyła Twój tekst do naszego workera. Worker przesyła go następnie do Claude firmy Anthropic, który zamienia Twoje słowa na filtry. Nie zapisujemy Twojego tekstu.
  • Subskrybowanie lub wysyłanie opinii zapisuje to, co nam przesyłasz. Obejmuje to Twoje powiadomienie lub wiadomość oraz Twój e-mail, jeśli go podasz. Przechowujemy też podstawowe informacje o Twoim żądaniu, takie jak adres IP i przeglądarka. Przechowujemy to krótko, aby blokować spam i nadużycia. Każdy e-mail z powiadomieniem zawiera link do wypisania się jednym kliknięciem.
  • Linki z e-maili i pinezki — kliknięcie linku z e-maila z powiadomieniem loguje Cię na tym urządzeniu, aby przypięte elementy mogły za Tobą podążać. Krótki baner informuje, że jesteś zalogowany, i pozwala się wylogować („To nie Ty?”). Zmiana lub anulowanie subskrypcji nadal wymaga własnego linku z e-maila.
  • Odsłony stron są śledzone za pomocą Cloudflare Web Analytics. Nie używa plików cookie i pokazuje tylko sumy. Liczy wizyty. Nie wie, kim jesteś, ani nie śledzi Cię na innych stronach.
  • Mapy kliknięć i przewijania (Microsoft Clarity, gdy włączony) pokazują, gdzie ludzie stukają lub przewijają, byśmy mogli poprawić trudne miejsca. Wpisany tekst i pola formularzy — w tym e-mail — są maskowane i nigdy nie są wysyłane jawnym tekstem. Clarity nie ładuje się, gdy przeglądarka sygnalizuje Do Not Track lub Global Privacy Control. Nie służy do reklam ani profili osobistych.
  • ", session_signed_in: "Zalogowano linkiem z e-maila — pinezki mogą podążać za Tobą na tym urządzeniu.", session_not_you: "To nie Ty?", session_dismiss: "Zamknij", diff --git a/site/i18n/lang/ru.js b/site/i18n/lang/ru.js index 6ee517e4..7f75f349 100644 --- a/site/i18n/lang/ru.js +++ b/site/i18n/lang/ru.js @@ -913,7 +913,7 @@ about_foot_html: "CityScroll · поисковый интерфейс к The City Record (открывается в новой вкладке) · Журнал изменений · Статистика · Главная", about_h_privacy: "Конфиденциальность", about_p_privacy_intro: "У CityScroll нет аккаунтов с паролем и рекламных технологий. Мы не отслеживаем вас на других сайтах.", - about_li_privacy_html: "
  • Запросы и фильтры используют NYC Open Data.
  • Поле «Ask» позволяет искать простым английским языком. Ваш текст отправляется в Claude от Anthropic, который превращает его в фильтры. Мы не сохраняем ваш текст.
  • Подписка или отправка отзыва сохраняет то, что вы нам присылаете. Это включает ваше оповещение или сообщение и ваш email, если вы его указываете. Мы также храним базовую информацию о вашем запросе, например ваш IP-адрес и браузер. Мы храним это недолго, чтобы блокировать спам и злоупотребления. В каждом письме-оповещении есть ссылка отписки в один клик.
  • Ссылки из писем и закрепления — переход по ссылке из письма-оповещения вводит вас в сессию на этом устройстве, чтобы закреплённые элементы могли следовать за вами. Короткая плашка сообщает, что вы вошли, и позволяет выйти («Не вы?»). Изменение или отмена подписки по-прежнему требуют отдельной ссылки из письма.
  • Просмотры страниц отслеживаются через Cloudflare Web Analytics. Он не использует cookie и показывает только итоги. Он считает визиты. Он не знает, кто вы, и не следит за вами на других сайтах.
  • ", + about_li_privacy_html: "
  • Запросы и фильтры используют NYC Open Data.
  • Поле «Ask» позволяет искать простым английским языком. Ваш текст отправляется в Claude от Anthropic, который превращает его в фильтры. Мы не сохраняем ваш текст.
  • Подписка или отправка отзыва сохраняет то, что вы нам присылаете. Это включает ваше оповещение или сообщение и ваш email, если вы его указываете. Мы также храним базовую информацию о вашем запросе, например ваш IP-адрес и браузер. Мы храним это недолго, чтобы блокировать спам и злоупотребления. В каждом письме-оповещении есть ссылка отписки в один клик.
  • Ссылки из писем и закрепления — переход по ссылке из письма-оповещения вводит вас в сессию на этом устройстве, чтобы закреплённые элементы могли следовать за вами. Короткая плашка сообщает, что вы вошли, и позволяет выйти («Не вы?»). Изменение или отмена подписки по-прежнему требуют отдельной ссылки из письма.
  • Просмотры страниц отслеживаются через Cloudflare Web Analytics. Он не использует cookie и показывает только итоги. Он считает визиты. Он не знает, кто вы, и не следит за вами на других сайтах.
  • Карты кликов и прокрутки (Microsoft Clarity, когда включён) показывают, куда люди нажимают или прокручивают, чтобы мы могли исправить неудобные места. Введённый текст и поля форм — включая email — маскируются и никогда не отправляются открытым текстом. Clarity не загружается, если браузер сигнализирует Do Not Track или Global Privacy Control. Не используется для рекламы или личных профилей.
  • ", session_signed_in: "Вход по ссылке из письма — закрепления могут следовать за вами на этом устройстве.", session_not_you: "Не вы?", session_dismiss: "Скрыть", diff --git a/site/i18n/lang/ur.js b/site/i18n/lang/ur.js index 3cb79f37..e1c57388 100644 --- a/site/i18n/lang/ur.js +++ b/site/i18n/lang/ur.js @@ -916,7 +916,7 @@ about_foot_html: "CityScroll · The City Record (نئے ٹیب میں کھلتا ہے) پر ایک تلاشی انٹرفیس · تبدیلیوں کی فہرست · اعداد و شمار · ہوم", about_h_privacy: "رازداری", about_p_privacy_intro: "CityScroll کے پاس پاس ورڈ والے اکاؤنٹس نہیں اور نہ ہی اشتہاری ٹیکنالوجی۔ یہ آپ کو دوسری سائٹس پر ٹریک نہیں کرتا۔", - about_li_privacy_html: "
  • تلاشیں اور فلٹرز NYC Open Data استعمال کرتے ہیں۔
  • \"پوچھیں\" باکس آپ کو سادہ انگریزی میں تلاش کرنے دیتا ہے۔ آپ کا متن Anthropic کے Claude کو بھیجا جاتا ہے، جو اسے فلٹرز میں بدل دیتا ہے۔ ہم آپ کا متن محفوظ نہیں کرتے۔
  • سبسکرائب کرنا یا رائے بھیجنا وہ محفوظ کرتا ہے جو آپ ہمیں بھیجتے ہیں۔ اس میں آپ کا الرٹ یا پیغام اور آپ کا ای میل شامل ہے، اگر آپ کوئی شیئر کریں۔ ہم آپ کی درخواست کے بارے میں کچھ بنیادی معلومات بھی رکھتے ہیں، جیسے آپ کا IP پتہ اور براؤزر۔ ہم اسپیم اور بدسلوکی روکنے کے لیے یہ کچھ عرصے تک رکھتے ہیں۔ ہر الرٹ ای میل میں ایک کلک سے سبسکرپشن منسوخ کرنے کا لنک ہوتا ہے۔
  • ای میل لنکس اور پن — الرٹ ای میل کے لنک پر کلک کرنے سے اس آلے پر آپ سائن ان ہو جاتے ہیں تاکہ آپ کی پن کی گئی اشیاء آپ کے ساتھ رہ سکیں۔ ایک مختصر بینر بتاتا ہے کہ آپ سائن ان ہیں اور سائن آؤٹ کرنے دیتا ہے (\"آپ نہیں؟\")۔ سبسکرپشن تبدیل یا منسوخ کرنے کے لیے اب بھی الگ ای میل لنک درکار ہے۔
  • صفحہ کے وزٹس کو Cloudflare Web Analytics سے ٹریک کیا جاتا ہے۔ یہ کوئی کوکیز استعمال نہیں کرتا اور صرف کل تعداد دکھاتا ہے۔ یہ وزٹس شمار کرتا ہے۔ یہ نہیں جانتا کہ آپ کون ہیں یا آپ کو دوسری سائٹس تک فالو نہیں کرتا۔
  • ", + about_li_privacy_html: "
  • تلاشیں اور فلٹرز NYC Open Data استعمال کرتے ہیں۔
  • \"پوچھیں\" باکس آپ کو سادہ انگریزی میں تلاش کرنے دیتا ہے۔ آپ کا متن Anthropic کے Claude کو بھیجا جاتا ہے، جو اسے فلٹرز میں بدل دیتا ہے۔ ہم آپ کا متن محفوظ نہیں کرتے۔
  • سبسکرائب کرنا یا رائے بھیجنا وہ محفوظ کرتا ہے جو آپ ہمیں بھیجتے ہیں۔ اس میں آپ کا الرٹ یا پیغام اور آپ کا ای میل شامل ہے، اگر آپ کوئی شیئر کریں۔ ہم آپ کی درخواست کے بارے میں کچھ بنیادی معلومات بھی رکھتے ہیں، جیسے آپ کا IP پتہ اور براؤزر۔ ہم اسپیم اور بدسلوکی روکنے کے لیے یہ کچھ عرصے تک رکھتے ہیں۔ ہر الرٹ ای میل میں ایک کلک سے سبسکرپشن منسوخ کرنے کا لنک ہوتا ہے۔
  • ای میل لنکس اور پن — الرٹ ای میل کے لنک پر کلک کرنے سے اس آلے پر آپ سائن ان ہو جاتے ہیں تاکہ آپ کی پن کی گئی اشیاء آپ کے ساتھ رہ سکیں۔ ایک مختصر بینر بتاتا ہے کہ آپ سائن ان ہیں اور سائن آؤٹ کرنے دیتا ہے (\"آپ نہیں؟\")۔ سبسکرپشن تبدیل یا منسوخ کرنے کے لیے اب بھی الگ ای میل لنک درکار ہے۔
  • صفحہ کے وزٹس کو Cloudflare Web Analytics سے ٹریک کیا جاتا ہے۔ یہ کوئی کوکیز استعمال نہیں کرتا اور صرف کل تعداد دکھاتا ہے۔ یہ وزٹس شمار کرتا ہے۔ یہ نہیں جانتا کہ آپ کون ہیں یا آپ کو دوسری سائٹس تک فالو نہیں کرتا۔
  • کلک اور سکرول نقشے (Microsoft Clarity، جب آن ہو) دکھاتے ہیں لوگ کہاں ٹیپ یا سکرول کرتے ہیں تاکہ ہم مشکل حصے ٹھیک کر سکیں۔ ٹائپ شدہ متن اور فارم فیلڈز — بشمول ای میل — ماسک ہوتے ہیں اور کبھی سادہ متن کے طور پر نہیں بھیجے جاتے۔ اگر آپ کا براؤزر Do Not Track یا Global Privacy Control کا اشارہ دے تو Clarity لوڈ نہیں ہوتا۔ یہ اشتہارات یا ذاتی پروفائلز کے لیے استعمال نہیں ہوتا۔
  • ", session_signed_in: "ای میل لنک سے سائن ان — آپ کی پن اس آلے پر آپ کے ساتھ رہ سکتی ہیں۔", session_not_you: "آپ نہیں؟", session_dismiss: "بند کریں", diff --git a/site/i18n/lang/zh-Hans.js b/site/i18n/lang/zh-Hans.js index ce206e56..c9bcccc0 100644 --- a/site/i18n/lang/zh-Hans.js +++ b/site/i18n/lang/zh-Hans.js @@ -901,7 +901,7 @@ about_foot_html: "CityScroll · 面向 The City Record (在新标签页中打开) 的搜索界面 · 更新日志 · 统计 · 首页", about_h_privacy: "隐私", about_p_privacy_intro: "CityScroll 没有密码账户,也没有广告技术。不会跨站追踪您。", - about_li_privacy_html: "
  • 搜索和筛选使用 NYC Open Data。
  • 「Ask」框让您可以用普通英语搜索。您的文字会发送给 Anthropic 的 Claude,由它转换成筛选条件。我们不保存您的文字。
  • 订阅或发送反馈会保存您发给我们的内容。这包括您的提醒或消息以及您的邮箱(如果您提供)。我们还会保留一些关于您请求的基本信息,如您的 IP 地址和浏览器。我们会将其保留一小段时间,以阻止垃圾信息和滥用。每封提醒邮件都有一键取消订阅链接。
  • 邮件链接与置顶 — 点击提醒邮件中的链接会在该设备上登录,以便您的置顶内容可以跟着您。简短横幅会说明您已登录,并允许退出(「不是您?」)。更改或取消订阅仍需要邮件中的专用链接。
  • 页面访问量通过 Cloudflare Web Analytics 跟踪。它不使用 cookie,只显示总数。它统计访问次数。它不知道您是谁,也不会跟踪您到其他网站。
  • ", + about_li_privacy_html: "
  • 搜索和筛选使用 NYC Open Data。
  • 「Ask」框让您可以用普通英语搜索。您的文字会发送给 Anthropic 的 Claude,由它转换成筛选条件。我们不保存您的文字。
  • 订阅或发送反馈会保存您发给我们的内容。这包括您的提醒或消息以及您的邮箱(如果您提供)。我们还会保留一些关于您请求的基本信息,如您的 IP 地址和浏览器。我们会将其保留一小段时间,以阻止垃圾信息和滥用。每封提醒邮件都有一键取消订阅链接。
  • 邮件链接与置顶 — 点击提醒邮件中的链接会在该设备上登录,以便您的置顶内容可以跟着您。简短横幅会说明您已登录,并允许退出(「不是您?」)。更改或取消订阅仍需要邮件中的专用链接。
  • 页面访问量通过 Cloudflare Web Analytics 跟踪。它不使用 cookie,只显示总数。它统计访问次数。它不知道您是谁,也不会跟踪您到其他网站。
  • 点击与滚动热图(Microsoft Clarity,开启时)显示人们点击或滚动的位置,以便我们改进难用的部分。输入的文字和表单字段——包括邮箱——会被遮盖,绝不以明文发送。若浏览器发出“请勿跟踪”或全球隐私控制信号,则不加载 Clarity。不用于广告或个人画像。
  • ", session_signed_in: "已通过邮件链接登录 — 您的置顶可在此设备上跟随您。", session_not_you: "不是您?", session_dismiss: "关闭", diff --git a/site/index.html b/site/index.html index 370de6c5..450d624e 100644 --- a/site/index.html +++ b/site/index.html @@ -673,6 +673,7 @@ + @@ -1197,7 +1198,7 @@

    Get your digest in
    - +
    diff --git a/site/standards.html b/site/standards.html index f1a001e0..571d028c 100644 --- a/site/standards.html +++ b/site/standards.html @@ -23,6 +23,7 @@ + diff --git a/site/stats.html b/site/stats.html index 830758bf..88d37fa6 100644 --- a/site/stats.html +++ b/site/stats.html @@ -23,6 +23,7 @@ + diff --git a/test/analytics_readiness.test.mjs b/test/analytics_readiness.test.mjs index e115ded9..284897e0 100644 --- a/test/analytics_readiness.test.mjs +++ b/test/analytics_readiness.test.mjs @@ -356,12 +356,16 @@ test("privacy copy removes falsified exhaustive promises without adding a new en const english = await readFile(new URL("../site/i18n.js", import.meta.url), "utf8"); for (const source of [about, english]) { // Honest intro: no password accounts / no ad tech / no cross-site tracking. - // Session cookies exist only after an alert-email magic link (pins sync) — do not + // Session cookies exist only after an alert-email magic link (pins sync). Optional + // heatmaps (Clarity) may also set first-party cookies when configured — do not // re-promise a blanket "no cookies" line that the product no longer keeps. assert.match(source, /no password accounts and no ad tech/i); assert.match(source, /does not track you across other sites/i); assert.match(source, /Searches and filters(?:<\/b>)? use NYC Open Data/); assert.match(source, /Email links and pins/i); + // Optional heatmaps disclosed in plain language (masking + opt-out), not as tech inventory. + assert.match(source, /Microsoft Clarity/); + assert.match(source, /Do Not Track|Global Privacy Control/); assert.doesNotMatch(source, /go straight to NYC Open Data|server never sees them|only keep a daily count/i); assert.doesNotMatch(source, /aggregate usage events|interaction taxonomy/i); assert.doesNotMatch(source, /uses no accounts, no cookies, no cross-site tracking, no ad tech/); diff --git a/test/clarity.test.mjs b/test/clarity.test.mjs new file mode 100644 index 00000000..cf966d26 --- /dev/null +++ b/test/clarity.test.mjs @@ -0,0 +1,199 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; +import vm from "node:vm"; + +const claritySource = readFileSync(new URL("../site/clarity.js", import.meta.url), "utf8"); + +function loadClarity(sandboxExtras = {}) { + const sandbox = { + window: undefined, + globalThis: undefined, + document: undefined, + navigator: undefined, + ...sandboxExtras, + }; + sandbox.window = sandbox.window || sandbox; + sandbox.globalThis = sandbox; + vm.createContext(sandbox); + vm.runInContext(claritySource, sandbox); + return sandbox.CROLClarity; +} + +function fakeDoc(html = "") { + const store = new Map(); + const elements = []; + const doc = { + readyState: "complete", + head: { appendChild(node) { elements.push(node); return node; } }, + body: null, + documentElement: null, + createElement(tag) { + const el = { + tagName: tag.toUpperCase(), + async: false, + src: "", + onerror: null, + attrs: {}, + setAttribute(k, v) { this.attrs[k] = v; }, + getAttribute(k) { return this.attrs[k]; }, + }; + return el; + }, + querySelector(sel) { + if (sel.startsWith('meta[name="crol-clarity-project-id"]')) { + const content = store.get("meta:crol-clarity-project-id"); + return content == null ? null : { content }; + } + return null; + }, + querySelectorAll(sel) { + if (sel === "input, textarea, select") return store.get("formFields") || []; + return []; + }, + getElementById(id) { + const map = store.get("byId") || {}; + return map[id] || null; + }, + _store: store, + _scripts: elements, + }; + return doc; +} + +function formField() { + const attrs = {}; + return { + attrs, + setAttribute(k, v) { attrs[k] = v; }, + getAttribute(k) { return attrs[k]; }, + }; +} + +test("dormant when project id is empty", () => { + const api = loadClarity(); + assert.equal(api.resolveProjectId({}, fakeDoc()), ""); + assert.equal(api.shouldLoad({ projectId: "", navigator: {} }), false); + const result = api.boot({ window: { CROL_CLARITY_PROJECT_ID: "" }, document: fakeDoc(), navigator: {} }); + assert.equal(result.loaded, false); + assert.equal(result.reason, "unconfigured"); +}); + +test("resolves project id from window, then meta", () => { + const api = loadClarity(); + assert.equal( + api.resolveProjectId({ CROL_CLARITY_PROJECT_ID: " abc123 " }, fakeDoc()), + "abc123", + ); + const doc = fakeDoc(); + doc._store.set("meta:crol-clarity-project-id", "meta-id-9"); + assert.equal(api.resolveProjectId({}, doc), "meta-id-9"); +}); + +test("skips load when Do Not Track is set", () => { + const api = loadClarity(); + assert.equal(api.prefersNoTracking({ doNotTrack: "1" }), true); + assert.equal(api.prefersNoTracking({ doNotTrack: "yes" }), true); + assert.equal(api.prefersNoTracking({ msDoNotTrack: "1" }), true); + assert.equal(api.shouldLoad({ projectId: "abc", navigator: { doNotTrack: "1" } }), false); + const doc = fakeDoc(); + const result = api.boot({ + window: { CROL_CLARITY_PROJECT_ID: "abc" }, + document: doc, + navigator: { doNotTrack: "1" }, + }); + assert.equal(result.loaded, false); + assert.equal(result.reason, "opt-out"); + assert.equal(doc._scripts.length, 0); +}); + +test("skips load when Global Privacy Control is set", () => { + const api = loadClarity(); + assert.equal(api.prefersNoTracking({ globalPrivacyControl: true }), true); + assert.equal( + api.shouldLoad({ projectId: "abc", navigator: { globalPrivacyControl: true } }), + false, + ); + const result = api.boot({ + window: { CROL_CLARITY_PROJECT_ID: "abc" }, + document: fakeDoc(), + navigator: { globalPrivacyControl: true }, + }); + assert.equal(result.loaded, false); + assert.equal(result.reason, "opt-out"); +}); + +test("loads async Clarity tag when configured and not opted out", () => { + const api = loadClarity(); + const doc = fakeDoc(); + const email = formField(); + doc._store.set("formFields", [email]); + doc._store.set("byId", { adest: email }); + const result = api.boot({ + window: { CROL_CLARITY_PROJECT_ID: "projectXYZ" }, + document: doc, + navigator: { doNotTrack: "0", globalPrivacyControl: false }, + }); + assert.equal(result.loaded, true); + assert.equal(result.reason, "ok"); + assert.equal(doc._scripts.length, 1); + assert.equal(doc._scripts[0].async, true); + assert.equal(doc._scripts[0].src, "https://www.clarity.ms/tag/projectXYZ"); + assert.equal(email.getAttribute("data-clarity-mask"), "true"); +}); + +test("masks all form controls including known email ids", () => { + const api = loadClarity(); + const doc = fakeDoc(); + const input = formField(); + const textarea = formField(); + const email = formField(); + doc._store.set("formFields", [input, textarea]); + doc._store.set("byId", { fbemail: email }); + const count = api.applyInputMasking(doc); + assert.equal(count, 2); + assert.equal(input.getAttribute("data-clarity-mask"), "true"); + assert.equal(textarea.getAttribute("data-clarity-mask"), "true"); + assert.equal(email.getAttribute("data-clarity-mask"), "true"); +}); + +test("every public page loads the Clarity runtime", () => { + for (const page of [ + "index.html", + "about.html", + "api.html", + "changelog.html", + "data.html", + "standards.html", + "stats.html", + ]) { + const source = readFileSync(new URL(`../site/${page}`, import.meta.url), "utf8"); + assert.match(source, /