Skip to content
4 changes: 2 additions & 2 deletions src/components/NavBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@
};
</script>

<header class="sticky top-0 bg-white/90 border-b border-gray-200 shadow-sm" style="z-index: 1000;">
<header class="sticky top-0 z-[1000] bg-white/90 border-b border-gray-200 shadow-sm">
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8 flex justify-between items-center h-20">
<!-- Logo -->
<div class="flex gap-8">
<div class="flex-shrink-0">
<a href="/" class="flex items-center group">
<img src="/images/new-homepage/t4p-logo.webp" alt="T4P logo" class="transition-transform duration-200 group-hover:scale-105" style="height: 4.5rem;" />
<img src="/images/new-homepage/t4p-logo.webp" alt="T4P logo" class="h-[4.5rem] transition-transform duration-200 group-hover:scale-105" />
</a>
</div>

Expand Down
2 changes: 1 addition & 1 deletion src/components/TFPLogo.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
>
<mask
id="mask0_5_2"
style="mask-type:alpha"
class="[mask-type:alpha]"
maskUnits="userSpaceOnUse"
x="0"
y="0"
Expand Down
86 changes: 76 additions & 10 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,81 @@ const canonicalUrl = new URL(
}
</script>
<meta name="generator" content={Astro.generator} />
<meta name="csp-nonce" content={Astro.locals.cspNonce} />
<script is:inline>
(function () {
function applyStyleString(el, value) {
while (el.style.length > 0) el.style.removeProperty(el.style.item(0));
if (!value) return;
value.split(';').forEach(function (part) {
var idx = part.indexOf(':');
if (idx > 0) {
var prop = part.slice(0, idx).trim();
var val = part.slice(idx + 1).trim();
if (!prop) return;
var priority = '';
if (/!important\s*$/.test(val)) {
priority = 'important';
val = val.replace(/\s*!important\s*$/, '').trim();
}
el.style.setProperty(prop, val, priority);
}
});
}

/* Patch 1: auto-nonce <style> elements created by JS (emotion/MUI) */
var m = document.querySelector('meta[name="csp-nonce"]');
var n = m && m.getAttribute('content');
if (n) {
var origCreate = document.createElement.bind(document);
document.createElement = function (tag) {
var el = origCreate.apply(this, arguments);
if (typeof tag === 'string' && tag.toLowerCase() === 'style') el.nonce = n;
return el;
};
}

/* Patch 2: convert setAttribute('style', ...) to individual setProperty() calls.
Handles third-party scripts that mutate style via setAttribute. */
var origSetAttr = Element.prototype.setAttribute;
Element.prototype.setAttribute = function (name, value) {
if (name === 'style') { applyStyleString(this, value); return; }
origSetAttr.apply(this, arguments);
};

/* Patch 3: strip style="..." from innerHTML before the browser parses it.
Handles third-party scripts (e.g. Email Octopus) that inject styled HTML
via innerHTML — the browser blocks style attributes at parse time before
any setAttribute intercept can fire. We rename them to data-csp-style,
then re-apply via CSSOM after insertion. */
var origInnerHTMLDesc = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
if (origInnerHTMLDesc && origInnerHTMLDesc.set) {
Object.defineProperty(Element.prototype, 'innerHTML', {
get: origInnerHTMLDesc.get,
set: function (value) {
if (typeof value !== 'string' || value.indexOf('style=') === -1) {
origInnerHTMLDesc.set.call(this, value);
return;
}
// Rename style="..." / style='...' → data-csp-style so the parser never sees them
var cleaned = value
.replace(/\bstyle\s*=\s*"([^"]*)"/gi, 'data-csp-style="$1"')
.replace(/\bstyle\s*=\s*'([^']*)'/gi, "data-csp-style='$1'");
origInnerHTMLDesc.set.call(this, cleaned);
// Re-apply stored styles via CSSOM
var els = this.querySelectorAll('[data-csp-style]');
for (var i = 0; i < els.length; i++) {
var sv = els[i].getAttribute('data-csp-style');
els[i].removeAttribute('data-csp-style');
applyStyleString(els[i], sv);
}
},
configurable: true,
enumerable: origInnerHTMLDesc.enumerable,
});
}
})();
</script>
<title>{title}</title>
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
Expand Down Expand Up @@ -73,16 +148,7 @@ const canonicalUrl = new URL(
<!-- Floating Donate Button -->
<a
href="/donate"
class="donate-link hidden md:block fixed right-0 top-1/2 z-50 bg-green-800 px-3 py-5 text-white shadow-lg transition hover:bg-green-900"
style="
writing-mode: vertical-rl;
transform: translateY(-50%) rotate(180deg);
transform-origin: center center;
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
"
class="donate-link hidden md:block fixed right-0 top-1/2 z-50 bg-green-800 px-3 py-5 text-white shadow-lg transition hover:bg-green-900 [writing-mode:vertical-rl] -translate-y-1/2 rotate-180 origin-center rounded-tr-lg rounded-br-lg rounded-tl-none rounded-bl-none"
>
Donate
</a>
Expand Down
26 changes: 16 additions & 10 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export const onRequest = defineMiddleware(async (context, next) => {
"default-src 'self'",
// 'strict-dynamic' trusts scripts loaded by nonced scripts; removes need for 'unsafe-inline'
`script-src 'nonce-${nonce}' 'strict-dynamic' https://secure.qgiv.com https://plausible.io https://pal-chat.net https://techforpalestine.org/cdn-cgi/`,
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"font-src 'self' https://fonts.gstatic.com",
`style-src 'nonce-${nonce}' 'self' https://fonts.googleapis.com https://secure.qgiv.com`,
"font-src 'self' https://fonts.gstatic.com https://gallery.eo.page",
"img-src 'self' data: https:",
"connect-src 'self' https://plausible.io https://pal-chat.net",
"frame-src https://secure.qgiv.com https://calendly.com https://www.youtube.com https://www.youtube-nocookie.com",
"frame-src https://secure.qgiv.com https://calendly.com https://www.youtube.com https://www.youtube-nocookie.com https://www.google.com https://validaid.org",
"object-src 'none'",
"base-uri 'self'",
].join("; ");
Expand All @@ -37,13 +37,19 @@ export const onRequest = defineMiddleware(async (context, next) => {
return response;
}

// Inject nonce into every <script> tag so Astro's hydration scripts and
// inline scripts are all covered by the nonce-based allowlist.
const rewriter = new HTMLRewriter().on("script", {
element(el) {
el.setAttribute("nonce", nonce);
},
});
// Inject nonce into every <script> and <style> tag so Astro's hydration
// scripts and any server-rendered inline styles are covered by the nonce.
const rewriter = new HTMLRewriter()
.on("script", {
element(el) {
el.setAttribute("nonce", nonce);
},
})
.on("style", {
element(el: { setAttribute(name: string, value: string): void }) {
el.setAttribute("nonce", nonce);
},
});

const transformed = rewriter.transform(response);
transformed.headers.set("Content-Security-Policy", csp);
Expand Down
9 changes: 7 additions & 2 deletions src/pages/about.astro
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ import "../styles/base.css";
aria-hidden="true"
>
<div
class="aspect-[801/1036] w-[50.0625rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30"
style="clip-path: polygon(63.1% 29.5%, 100% 17.1%, 76.6% 3%, 48.4% 0%, 44.6% 4.7%, 54.5% 25.3%, 59.8% 49%, 55.2% 57.8%, 44.4% 57.2%, 27.8% 47.9%, 35.1% 81.5%, 0% 97.7%, 39.2% 100%, 35.2% 81.4%, 97.2% 52.8%, 63.1% 29.5%)"
class="gradient-blob aspect-[801/1036] w-[50.0625rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30"
>
</div>
</div>
Expand Down Expand Up @@ -360,3 +359,9 @@ import "../styles/base.css";
/>
</div>
</Layout>

<style>
.gradient-blob {
clip-path: polygon(63.1% 29.5%, 100% 17.1%, 76.6% 3%, 48.4% 0%, 44.6% 4.7%, 54.5% 25.3%, 59.8% 49%, 55.2% 57.8%, 44.4% 57.2%, 27.8% 47.9%, 35.1% 81.5%, 0% 97.7%, 39.2% 100%, 35.2% 81.4%, 97.2% 52.8%, 63.1% 29.5%);
}
</style>
6 changes: 3 additions & 3 deletions src/pages/api/project-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ async function proxy(request: Request, locals: unknown): Promise<Response> {
const url = new URL(request.url);
const path = url.searchParams.get("path");

if (!path || !path.startsWith("/")) {
return new Response(JSON.stringify({ error: "Invalid path" }), {
status: 400,
if (!path || !path.startsWith("/api/method/")) {
return new Response(JSON.stringify({ error: "Path not allowed" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
Expand Down
8 changes: 4 additions & 4 deletions src/pages/donate.astro
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const isUK = country === "GB";
<h2 class="mb-4 text-center text-xl font-semibold text-gray-800">Make a donation</h2>
<div class="mx-auto w-full max-w-lg">
<!-- Variant A: Qgiv Form Only (no link) -->
<div id="form-qgiv-only" style="display: none;">
<div id="form-qgiv-only" hidden>
<div
class="qgiv-embed-container"
data-qgiv-embed="true"
Expand All @@ -106,7 +106,7 @@ const isUK = country === "GB";
</div>

<!-- Variant B Non-UK: Qgiv Form + Link to ValidAid -->
<div id="form-qgiv-link" style="display: none;">
<div id="form-qgiv-link" hidden>
<p class="mb-4 text-center text-sm text-gray-700">
If you are in the UK, <a
href="?variant=validaid"
Expand All @@ -125,7 +125,7 @@ const isUK = country === "GB";
</div>

<!-- Variant B UK: ValidAid Form + Link to Qgiv -->
<div id="form-validaid-link" style="display: none;">
<div id="form-validaid-link" hidden>
<p class="mb-4 text-center text-sm text-gray-700">
Not in the UK? <a
href="?variant=qgiv"
Expand All @@ -137,7 +137,7 @@ const isUK = country === "GB";
id="validaid-iframe-event-236"
src="https://validaid.org/embed/event/236"
title="Tech for Palestine"
style="width:100%; border:none;"
class="w-full border-0"
allow="payment"></iframe>
</div>
</div>
Expand Down
5 changes: 1 addition & 4 deletions src/pages/faq.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ import "../styles/base.css";
</main>

<!-- FAQ List Section -->
<section style="
max-width: 700px;
margin: 2rem auto;
padding: 0 1rem;">
<section class="max-w-[700px] mx-auto my-8 px-4">
<FAQList faqs={[]} client:only="react" />
</section>
</Layout>
24 changes: 9 additions & 15 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ import "../styles/base.css";
<div class="w-full space-y-6 px-6 md:px-12 lg:px-16 lg:pr-8 2xl:px-28">
<!-- Colored bars -->
<div class="flex items-center gap-1.5">
<div class="lg:w-22 h-2 w-24 rounded-full" style="background-color: #268024;"></div>
<div class="h-2 w-8 rounded-full lg:w-8" style="background-color: #e4312b;"></div>
<div class="h-2 w-5 rounded-full lg:w-5" style="background-color: #000000;"></div>
<div class="lg:w-22 h-2 w-24 rounded-full bg-[#268024]"></div>
<div class="h-2 w-8 rounded-full lg:w-8 bg-[#e4312b]"></div>
<div class="h-2 w-5 rounded-full lg:w-5 bg-black"></div>
</div>

<h1 class="text-4xl font-bold md:text-5xl">
Expand Down Expand Up @@ -136,8 +136,7 @@ import "../styles/base.css";
<img
src="/images/new-homepage/upscrolled_logo.webp"
alt="Upscrolled logo"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110"
style="border-radius: 2.2rem;"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110 rounded-[2.2rem]"
/>
</div>
<h3 class="mb-3 text-xl font-bold text-gray-900 group-hover:text-green-800">
Expand All @@ -162,8 +161,7 @@ import "../styles/base.css";
<img
src="/images/new-homepage/thaura.jpeg"
alt="Thaura logo"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110"
style="border-radius: 2.2rem;"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110 rounded-[2.2rem]"
/>
</div>
<h3 class="mb-3 text-xl font-bold text-gray-900 group-hover:text-green-800">Thaura</h3>
Expand All @@ -188,8 +186,7 @@ import "../styles/base.css";
<img
src="/images/new-homepage/Apricot.webp"
alt="Apricot logo"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110"
style="border-radius: 2.2rem;"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110 rounded-[2.2rem]"
/>
</div>
<h3 class="mb-3 text-xl font-bold text-gray-900 group-hover:text-green-800">
Expand Down Expand Up @@ -217,8 +214,7 @@ import "../styles/base.css";
<img
src="/images/new-homepage/Media_Bias.webp"
alt="Media Bias Meter logo"
class="w-full object-contain transition-transform duration-300 group-hover:scale-110"
style="border-radius: 2.2rem;"
class="w-full object-contain transition-transform duration-300 group-hover:scale-110 rounded-[2.2rem]"
/>
</div>
<h3 class="mb-3 text-xl font-bold text-gray-900 group-hover:text-green-800">
Expand Down Expand Up @@ -271,8 +267,7 @@ import "../styles/base.css";
<img
src="/images/new-homepage/Boycat_logo.webp"
alt="Boycat logo"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110"
style="border-radius: 2.2rem;"
class="h-full w-full object-contain transition-transform duration-300 group-hover:scale-110 rounded-[2.2rem]"
/>
</div>
<h3 class="mb-3 text-xl font-bold text-gray-900 group-hover:text-green-800">Boycat</h3>
Expand All @@ -295,8 +290,7 @@ import "../styles/base.css";
<img
src="/images/new-homepage/findaprotest_b.webp"
alt="Find a Protest logo"
class="h-full w-full object-contain px-5 transition-transform duration-300 group-hover:scale-110"
style="border-radius: 2.2rem; background-color: #9FBAA9;"
class="h-full w-full object-contain px-5 transition-transform duration-300 group-hover:scale-110 rounded-[2.2rem] bg-[#9FBAA9]"
/>
</div>
<h3 class="mb-3 text-xl font-bold text-gray-900 group-hover:text-green-800">
Expand Down
5 changes: 2 additions & 3 deletions src/pages/membership.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ import "../styles/base.css";
/>

<!-- Hero banner -->
<div class="w-full overflow-hidden" style="height: 420px;">
<div class="w-full overflow-hidden h-[420px]">
<img
src="/images/membership-hero.jpg"
alt="Tech for Palestine community members applauding at an event"
class="h-full w-full object-cover object-center"
style="object-position: center 30%;"
class="h-full w-full object-cover [object-position:center_30%]"
/>
</div>

Expand Down
9 changes: 7 additions & 2 deletions src/pages/project-details-temp.astro
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ const staticProject = {
aria-hidden="true"
>
<div
class="aspect-[801/1036] w-[50.0625rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30"
style="clip-path: polygon(63.1% 29.5%, 100% 17.1%, 76.6% 3%, 48.4% 0%, 44.6% 4.7%, 54.5% 25.3%, 59.8% 49%, 55.2% 57.8%, 44.4% 57.2%, 27.8% 47.9%, 35.1% 81.5%, 0% 97.7%, 39.2% 100%, 35.2% 81.4%, 97.2% 52.8%, 63.1% 29.5%)"
class="gradient-blob aspect-[801/1036] w-[50.0625rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30"
>
</div>
</div>
Expand All @@ -62,3 +61,9 @@ const staticProject = {
</h1>
</main>
</Layout>

<style>
.gradient-blob {
clip-path: polygon(63.1% 29.5%, 100% 17.1%, 76.6% 3%, 48.4% 0%, 44.6% 4.7%, 54.5% 25.3%, 59.8% 49%, 55.2% 57.8%, 44.4% 57.2%, 27.8% 47.9%, 35.1% 81.5%, 0% 97.7%, 39.2% 100%, 35.2% 81.4%, 97.2% 52.8%, 63.1% 29.5%);
}
</style>
3 changes: 1 addition & 2 deletions src/pages/team.astro
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,7 @@ import "../styles/base.css";
<img
src="/images/steering-team/tom.jpg"
alt="Tom Hall"
class="h-full w-full object-cover"
style="object-position: center 43%"
class="h-full w-full object-cover [object-position:center_43%]"
/>
</div>
<div class="p-6">
Expand Down
Loading