diff --git a/.gitignore b/.gitignore index b20740ac..4d2edbf1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # build output dist/ +.wrangler/ # generated types .astro/ diff --git a/.impeccable/design.json b/.impeccable/design.json new file mode 100644 index 00000000..7b71ba58 --- /dev/null +++ b/.impeccable/design.json @@ -0,0 +1,136 @@ +{ + "schemaVersion": 2, + "generatedAt": "2026-05-25T00:00:00Z", + "title": "Design System: Tech for Palestine", + "extensions": { + "colorMeta": { + "page": { "role": "neutral", "displayName": "Warm Parchment", "canonical": "oklch(99% 0.005 80)", "tonalRamp": ["oklch(15% 0.005 80)","oklch(30% 0.005 80)","oklch(45% 0.005 80)","oklch(60% 0.005 80)","oklch(75% 0.005 80)","oklch(88% 0.005 80)","oklch(95% 0.005 80)","oklch(99% 0.005 80)"] }, + "cream": { "role": "neutral", "displayName": "Cream", "canonical": "oklch(97% 0.008 65)", "tonalRamp": ["oklch(15% 0.008 65)","oklch(30% 0.008 65)","oklch(45% 0.008 65)","oklch(60% 0.008 65)","oklch(75% 0.008 65)","oklch(88% 0.008 65)","oklch(94% 0.008 65)","oklch(97% 0.008 65)"] }, + "butter": { "role": "neutral", "displayName": "Butter", "canonical": "oklch(95% 0.015 75)", "tonalRamp": ["oklch(15% 0.015 75)","oklch(30% 0.015 75)","oklch(45% 0.015 75)","oklch(60% 0.015 75)","oklch(75% 0.015 75)","oklch(87% 0.015 75)","oklch(92% 0.015 75)","oklch(95% 0.015 75)"] }, + "sand": { "role": "neutral", "displayName": "Sand", "canonical": "oklch(96% 0.010 80)", "tonalRamp": ["oklch(15% 0.010 80)","oklch(30% 0.010 80)","oklch(45% 0.010 80)","oklch(60% 0.010 80)","oklch(75% 0.010 80)","oklch(88% 0.010 80)","oklch(93% 0.010 80)","oklch(96% 0.010 80)"] }, + "ink": { "role": "neutral", "displayName": "Ink", "canonical": "oklch(20% 0.008 350)", "tonalRamp": ["oklch(20% 0.008 350)","oklch(32% 0.008 350)","oklch(44% 0.008 350)","oklch(56% 0.008 350)","oklch(68% 0.008 350)","oklch(80% 0.008 350)","oklch(90% 0.008 350)","oklch(97% 0.008 350)"] }, + "ink-secondary":{ "role": "neutral", "displayName": "Ink Secondary", "canonical": "oklch(48% 0.012 350)", "tonalRamp": ["oklch(15% 0.012 350)","oklch(28% 0.012 350)","oklch(40% 0.012 350)","oklch(48% 0.012 350)","oklch(62% 0.012 350)","oklch(74% 0.012 350)","oklch(85% 0.012 350)","oklch(95% 0.012 350)"] }, + "ink-muted": { "role": "neutral", "displayName": "Ink Muted", "canonical": "oklch(74% 0.002 0)", "tonalRamp": ["oklch(20% 0.002 0)","oklch(35% 0.002 0)","oklch(50% 0.002 0)","oklch(62% 0.002 0)","oklch(74% 0.002 0)","oklch(84% 0.002 0)","oklch(91% 0.002 0)","oklch(97% 0.002 0)"] }, + "ink-divider": { "role": "neutral", "displayName": "Ink Divider", "canonical": "oklch(85% 0.002 0)", "tonalRamp": ["oklch(20% 0.002 0)","oklch(35% 0.002 0)","oklch(50% 0.002 0)","oklch(65% 0.002 0)","oklch(75% 0.002 0)","oklch(85% 0.002 0)","oklch(92% 0.002 0)","oklch(97% 0.002 0)"] }, + "brand": { "role": "primary", "displayName": "Pomegranate", "canonical": "oklch(50% 0.12 352)", "tonalRamp": ["oklch(18% 0.12 352)","oklch(27% 0.12 352)","oklch(36% 0.12 352)","oklch(44% 0.12 352)","oklch(50% 0.12 352)","oklch(62% 0.12 352)","oklch(75% 0.10 352)","oklch(90% 0.06 352)"] }, + "brand-hover": { "role": "primary", "displayName": "Pomegranate Light", "canonical": "oklch(57% 0.13 352)", "tonalRamp": ["oklch(20% 0.13 352)","oklch(30% 0.13 352)","oklch(40% 0.13 352)","oklch(50% 0.13 352)","oklch(57% 0.13 352)","oklch(68% 0.11 352)","oklch(80% 0.08 352)","oklch(93% 0.04 352)"] } + }, + "typographyMeta": { + "display": { "displayName": "Display", "purpose": "Hero headlines. Used once per page." }, + "headline": { "displayName": "Headline/Editorial","purpose": "Section-opening statements. Manifesto, portfolio header." }, + "title": { "displayName": "Title", "purpose": "Project names, sub-section headings." }, + "body": { "displayName": "Body", "purpose": "Primary body copy. Max 65-75ch line length." }, + "label": { "displayName": "Label/Eyebrow", "purpose": "Section annotations, button text. Always uppercase." } + }, + "shadows": [ + { "name": "logo-frame-ambient", "value": "0 6px 20px rgba(0,0,0,0.06)", "purpose": "Rotated butter accent behind logo frames." }, + { "name": "logo-frame-lift", "value": "0 8px 24px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.05)", "purpose": "Logo card foreground — structural lift over accent." } + ], + "motion": [ + { "name": "blur-in", "value": "opacity 0 filter blur(12px) translateY(20px) → opacity 1 filter blur(0) translateY(0)", "duration": "1.2s", "easing": "ease-out", "purpose": "Hero headline entrance. Used for primary copy only." }, + { "name": "fade-up", "value": "opacity 0 translateY(12px) → opacity 1 translateY(0)", "duration": "0.6s", "easing": "ease-out", "purpose": "CTA and logo entrance after headline." }, + { "name": "card-in", "value": "opacity 0 scale(0.96) → opacity 1 scale(1)", "duration": "0.8s", "easing": "ease-out", "purpose": "Hero glass card entrance." }, + { "name": "ken-burns", "value": "scale(1) → scale(1.08) → scale(1)", "duration": "14s", "easing": "ease-in-out", "purpose": "Subtle image life. Full-bleed hero and collage photos only. Always motion-reduce:animate-none." }, + { "name": "portfolio-in", "value": "opacity 0 translateY(32px) → opacity 1 translateY(0)", "duration": "0.7s", "easing": "ease-out", "purpose": "Portfolio card scroll-triggered entrance." }, + { "name": "fade-left", "value": "opacity 0 translateX(-16px) → opacity 1 translateX(0)", "duration": "0.6s", "easing": "ease-out", "purpose": "Motif row icon stagger." } + ], + "breakpoints": [ + { "name": "sm", "value": "640px", "note": "Mobile collage and motif adjustments" }, + { "name": "md", "value": "810px", "note": "Hero layout switch: mobile scroll → desktop glass card" }, + { "name": "lg", "value": "1024px", "note": "Portfolio card 2-col, collage all 4 visible" }, + { "name": "xl", "value": "1200px", "note": "Logo repositions to bottom-right in hero card; type scale top step" } + ] + }, + "components": [ + { + "name": "Primary Button", + "kind": "button", + "refersTo": "button-primary", + "description": "Primary CTA. Used for Become a Member and Apply to Incubator.", + "html": "", + "css": ".ds-btn-primary { display: inline-flex; align-items: center; gap: 8px; font-family: 'Outfit', system-ui, sans-serif; font-size: 12px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; line-height: 1; color: #FFFBF5; background-color: #AB4956; border: 1px solid transparent; border-radius: 999px; padding: 14px 20px; cursor: pointer; transition: background-color 0.15s ease; } .ds-btn-primary:hover { background-color: #D35464; } .ds-btn-primary:active { transform: scale(0.98); } .ds-btn-primary:focus-visible { outline: 2px solid #AB4956; outline-offset: 2px; }" + }, + { + "name": "Ghost Button", + "kind": "button", + "refersTo": "button-ghost", + "description": "Secondary CTA. Used alongside Primary to offer a softer alternate action.", + "html": "", + "css": ".ds-btn-ghost { display: inline-flex; align-items: center; gap: 8px; font-family: 'Outfit', system-ui, sans-serif; font-size: 12px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; line-height: 1; color: #2A2428; background-color: transparent; border: 1px solid #2A2428; border-radius: 999px; padding: 14px 20px; cursor: pointer; transition: background-color 0.15s ease; } .ds-btn-ghost:hover { background-color: rgba(42,36,40,0.05); } .ds-btn-ghost:active { transform: scale(0.98); } .ds-btn-ghost:focus-visible { outline: 2px solid #AB4956; outline-offset: 2px; }" + }, + { + "name": "Text Link Button", + "kind": "button", + "refersTo": "button-text", + "description": "Inline text action. Used for tertiary options and body-copy links.", + "html": "", + "css": ".ds-btn-text { display: inline-flex; align-items: center; gap: 4px; font-family: 'Outfit', system-ui, sans-serif; font-size: 12px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; line-height: 1; color: #AB4956; background: none; border: none; border-radius: 0; padding: 0; cursor: pointer; transition: text-decoration 0.15s ease; } .ds-btn-text:hover { text-decoration: underline; } .ds-btn-text:focus-visible { outline: 2px solid #AB4956; outline-offset: 2px; }" + }, + { + "name": "Eyebrow Label", + "kind": "custom", + "refersTo": null, + "description": "Section annotation. Uppercase, tracked, Ink Secondary. Prefixed with an em-dash by convention.", + "html": "

— Our portfolio

", + "css": ".ds-eyebrow { font-family: 'Outfit', system-ui, sans-serif; font-size: 12px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; line-height: 1; color: #73656E; margin: 0; }" + }, + { + "name": "Portfolio Card", + "kind": "card", + "refersTo": "portfolio-card", + "description": "Signature content container. Full-width editorial strip, not a grid card. Part of the sticky-scroll stack on desktop.", + "html": "

UpScrolled

The social platform with no shadowbans and no algorithmic games. Every voice gets equal reach.

Global App Store
#1
\"UpScrolled
", + "css": ".ds-portfolio-card { display: grid; grid-template-columns: 1fr; gap: 32px; background: #F7F2E8; border: 1px solid #F7EAD4; border-radius: 20px; padding: 24px; } @media (min-width: 768px) { .ds-portfolio-card { grid-template-columns: 1.2fr 1fr; align-items: center; gap: 40px; padding: 40px; } } .ds-portfolio-card__content { display: flex; flex-direction: column; } .ds-portfolio-card__title { font-family: 'Fraunces', serif; font-size: clamp(32px, 3vw, 38px); font-weight: 400; line-height: 1.22; color: #2A2428; margin: 0; } .ds-portfolio-card__body { font-family: 'Outfit', system-ui, sans-serif; font-size: clamp(16px, 1.5vw, 18px); font-weight: 400; line-height: 1.48; color: #73656E; margin: 20px 0 0; } .ds-portfolio-card__stats { display: flex; flex-direction: column; gap: 24px; margin-top: 40px; } .ds-portfolio-card__stat-label { font-family: 'Outfit', system-ui, sans-serif; font-size: 18px; color: #73656E; margin: 0; } .ds-portfolio-card__stat-value { font-family: 'Fraunces', serif; font-size: clamp(42px, 4vw, 60px); font-weight: 400; line-height: 1; color: #AB4956; margin: 8px 0 0; } .ds-portfolio-card__logo { position: relative; width: 100%; min-height: 160px; } .ds-portfolio-logo-back { position: absolute; inset: 0; border-radius: 16px; background: #EFE3D4; transform: rotate(-3deg); box-shadow: 0 6px 20px rgba(0,0,0,0.06); } .ds-portfolio-logo-front { position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; min-height: 160px; border-radius: 16px; background: #fff; padding: 32px; box-shadow: 0 8px 24px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.05); }" + }, + { + "name": "Stats Cell", + "kind": "custom", + "refersTo": "stats-cell", + "description": "Impact number with supporting label. Lives inside the sand stats grid. Counter animates on scroll entry (JS); always degrades gracefully without animation.", + "html": "

80+

Projects Incubated

", + "css": ".ds-stat-cell { } .ds-stat-cell__value { font-family: 'Fraunces', serif; font-size: clamp(36px, 4vw, 48px); font-weight: 400; line-height: 1.22; color: #2A2428; margin: 0; } .ds-stat-cell__plus { color: #AB4956; } .ds-stat-cell__label { font-family: 'Outfit', system-ui, sans-serif; font-size: clamp(16px, 1.5vw, 20px); font-weight: 400; line-height: 1.48; color: #73656E; margin: 6px 0 0; }" + } + ], + "narrative": { + "northStar": "The Steadfast Press", + "overview": "This system is built like a publication produced by people who care deeply: warm paper stock, purposeful type, nothing decorative that doesn't earn its place. The Fraunces serif carries editorial authority; Outfit brings it back down to earth. The palette reads like late-afternoon light on linen, not a tech product, not a protest poster.\n\nThe design is warm without being sentimental, serious without being cold, and direct without being aggressive. It reflects a community of skilled professionals who know what they're doing and aren't apologetic about why. Complexity is prohibited not because the cause is simple, but because clarity is respect for the visitor's time and attention.\n\nThis system explicitly rejects: SaaS gradient aesthetics, corporate nonprofit polish, excessive motion or interactivity, vague social-good language, and anything that hedges on what T4P stands for.", + "keyCharacteristics": [ + "Editorial serif (Fraunces) paired with a clean humanist sans (Outfit)", + "Warm parchment neutrals with a single pomegranate accent, used sparingly", + "Flat-by-default surfaces with tonal depth through background steps", + "Motion is restrained: entrances only, no looping decorative animation", + "Every screen ends with a clear, direct call to action" + ], + "rules": [ + { "name": "The One Accent Rule", "body": "Pomegranate appears on less than 10% of any screen. It marks action and emphasis only. A second accent would dilute it to decoration.", "section": "colors" }, + { "name": "The Tonal Depth Rule", "body": "Depth is created by stepping through the neutral stack (Parchment → Cream → Sand → Butter), not by drop shadows. Shadows are reserved for interactive elements that need structural lift.", "section": "colors" }, + { "name": "The No Pure Values Rule", "body": "Never use #000000 or #ffffff. Every neutral carries a warm tint. Pure values feel clinical; tinted neutrals feel inhabited.", "section": "colors" }, + { "name": "The Fraunces Hierarchy Rule", "body": "Fraunces is exclusively for display, headline, and title roles. It is never used for body, label, or interface copy.", "section": "typography" }, + { "name": "The Line Length Rule", "body": "Body copy never exceeds 75 characters per line. On wide viewports, constrain with max-w-[75ch] or equivalent.", "section": "typography" }, + { "name": "The Flat-By-Default Rule", "body": "A surface that doesn't move does not get a shadow. If adding a shadow is the first instinct, ask whether a background step solves the same problem.", "section": "elevation" }, + { "name": "The No Nested Card Rule", "body": "Never place a card inside a card. The logo frame within a portfolio card is a logo display element, not a card.", "section": "components" } + ], + "dos": [ + "Use Pomegranate (#AB4956) only on CTAs, brand emphasis, and active states", + "Step through the neutral stack to create depth rather than adding shadows", + "Use Fraunces exclusively for display, headline, and title roles", + "Cap body text at 65–75ch line length on all viewport widths", + "Guard every animation with prefers-reduced-motion", + "End every page section with a progression toward the primary CTA", + "Use real faces, real event photos, and real project logos", + "State T4P's mission explicitly and early — the cause is named, not implied" + ], + "donts": [ + "Don't use excessive interactivity or motion — clarity takes priority over spectacle", + "Don't hedge or use vague social-good language — T4P is explicitly pro-Palestine", + "Don't use cold or corporate visual language: blues, grays, geometric icons, corporate-sans", + "Don't use gradient text (background-clip: text) — use solid Pomegranate", + "Don't use side-stripe borders (border-left > 1px as colored accent)", + "Don't repeat the hero metric pattern (big number + small label + gradient) as SaaS feature grid", + "Don't use glassmorphism decoratively — the hero glass card is purposeful, everywhere else is solid", + "Don't use #000000 or #ffffff", + "Don't add shadows to surfaces that don't move or need structural lift", + "Don't use generic nonprofit design: blue-and-white palettes, stock photo heroes, 'we believe in a better world' copy" + ] + } +} diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 00000000..52d121b7 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,222 @@ +--- +name: Tech for Palestine +description: A community of professionals incubating tech and advocacy projects for Palestinian liberation. +colors: + page: "#FFFBF5" + cream: "#FBF2ED" + butter: "#F7EAD4" + sand: "#F7F2E8" + ink: "#2A2428" + ink-secondary: "#73656E" + ink-muted: "#B5B5B5" + ink-divider: "#D6D6D6" + brand: "#AB4956" + brand-hover: "#D35464" +typography: + display: + fontFamily: "Fraunces, serif" + fontSize: "clamp(42px, 5vw, 60px)" + fontWeight: 400 + lineHeight: 1.22 + letterSpacing: "normal" + headline: + fontFamily: "Fraunces, serif" + fontSize: "clamp(36px, 4vw, 48px)" + fontWeight: 400 + lineHeight: 1.18 + letterSpacing: "-0.01em" + title: + fontFamily: "Fraunces, serif" + fontSize: "clamp(32px, 3vw, 38px)" + fontWeight: 400 + lineHeight: 1.22 + body: + fontFamily: "Outfit, system-ui, sans-serif" + fontSize: "clamp(16px, 1.5vw, 20px)" + fontWeight: 400 + lineHeight: 1.48 + label: + fontFamily: "Outfit, system-ui, sans-serif" + fontSize: "12px" + fontWeight: 500 + lineHeight: 1 + letterSpacing: "0.12em" +rounded: + sm: "8px" + md: "16px" + lg: "24px" + pill: "999px" +spacing: + section-y: "96px" + section-y-lg: "128px" + container-x: "24px" + container-x-md: "40px" + card: "24px" + card-md: "40px" + card-lg: "56px" +components: + button-primary: + backgroundColor: "{colors.brand}" + textColor: "#FFFFFF" + rounded: "{rounded.pill}" + padding: "14px 20px" + typography: "{typography.label}" + button-primary-hover: + backgroundColor: "{colors.brand-hover}" + button-ghost: + backgroundColor: "transparent" + textColor: "{colors.ink}" + rounded: "{rounded.pill}" + padding: "14px 20px" + typography: "{typography.label}" + button-text: + backgroundColor: "transparent" + textColor: "{colors.brand}" + rounded: "0" + padding: "0" + typography: "{typography.label}" + portfolio-card: + backgroundColor: "{colors.sand}" + rounded: "20px" + padding: "{spacing.card}" + stats-cell: + backgroundColor: "{colors.sand}" + textColor: "{colors.ink}" +--- + +# Design System: Tech for Palestine + +## 1. Overview + +**Creative North Star: "The Steadfast Press"** + +This system is built like a publication produced by people who care deeply: warm paper stock, purposeful type, nothing decorative that doesn't earn its place. The Fraunces serif carries editorial authority; Outfit brings it back down to earth. The palette reads like late-afternoon light on linen, not a tech product, not a protest poster. + +The design is warm without being sentimental, serious without being cold, and direct without being aggressive. It reflects a community of skilled professionals who know what they're doing and aren't apologetic about why. Complexity is prohibited not because the cause is simple, but because clarity is respect for the visitor's time and attention. + +This system explicitly rejects: SaaS gradient aesthetics, corporate nonprofit polish, excessive motion or interactivity, vague social-good language, and anything that hedges on what T4P stands for. + +**Key Characteristics:** +- Editorial serif (Fraunces) paired with a clean humanist sans (Outfit) +- Warm parchment neutrals with a single pomegranate accent, used sparingly +- Flat-by-default surfaces with tonal depth through background steps (page → cream → sand → butter) +- Motion is restrained: entrances only, no looping decorative animation, always `prefers-reduced-motion` safe +- CTAs appear where they are contextually earned, not mandated at the end of every section + +## 2. Colors: The Parchment and Pomegranate Palette + +A warm neutral stack grounded in off-white parchment, with a single saturated accent used only where it signals action or emphasis. The palette reads as editorial, human, and grounded — not tech-blue, not activist-red, not nonprofit-green. + +### Primary +- **Pomegranate** (`#AB4956`): The sole accent. Used on CTAs, active states, brand emphasis, and the `text-brand` utility class. Forbidden as a background for large surface areas. Its rarity is its power. +- **Pomegranate Light** (`#D35464`): Hover state only. Never appears at rest. Lighter than base for a lift feel. + +### Neutral +- **Warm Parchment** (`#FFFBF5`): The page background. The surface everything rests on. Never pure white. +- **Cream** (`#FBF2ED`): Slightly elevated surface — light card backgrounds, hover fills. +- **Butter** (`#F7EAD4`): Card borders, warm dividers, the rotated accent behind logo frames. +- **Sand** (`#F7F2E8`): Recessed surfaces — stats grids, input backgrounds. +- **Ink** (`#2A2428`): All primary text. Warm near-black, never pure black. +- **Ink Secondary** (`#73656E`): Body copy, descriptions, supporting text. +- **Ink Muted** (`#B5B5B5`): Placeholders, disabled states, metadata. +- **Ink Divider** (`#D6D6D6`): Horizontal rules, section separators. + +### Named Rules +**The One Accent Rule.** Pomegranate appears on less than 10% of any screen. It marks action and emphasis only. A second accent would dilute it to decoration. + +**The Tonal Depth Rule.** Depth is created by stepping through the neutral stack (Parchment → Cream → Sand → Butter), not by drop shadows on every surface. Shadows are reserved for interactive elements that need structural lift. + +**The No Pure Values Rule.** Never use `#000000` or `#ffffff`. Every neutral carries a warm tint toward the brand hue. Pure values feel clinical; tinted neutrals feel inhabited. + +## 3. Typography + +**Display Font:** Fraunces (with Fraunces Placeholder, serif fallback) +**Body Font:** Outfit (with system-ui, sans-serif fallback) + +**Character:** Fraunces carries optical weight and editorial warmth — it reads like a serious publication, not a startup homepage. Outfit is clean and humanist, grounding the editorial tone in clarity. Neither font performs. Together they communicate: "people who know what they're doing built this." + +### Hierarchy + +- **Display** (400, clamp 42–60px, line-height 1.22): Hero headlines. Used once per page. The largest typographic statement. +- **Headline / Editorial** (400, clamp 36–48px, line-height 1.18, tracking –0.01em): Section-opening statements. The manifesto headline, portfolio section header. Slightly tighter tracking for editorial character. +- **Heading** (400, clamp 32–38px, line-height 1.22): Project names in portfolio cards. Sub-section headings. Maps to `.ts-heading`. +- **Body Large** (Outfit, 400, clamp 18–20px, line-height 1.48): Primary body copy. Section descriptions, project descriptions. Line length capped at 65–75ch. +- **Body** (Outfit, 400, clamp 16–18px, line-height 1.22): Secondary body copy, card support text. Use sparingly — prefer Body Large where possible. +- **Label / Eyebrow** (Outfit, 500, 12px, tracking 0.12em, uppercase): Section labels, button text, `— Our portfolio` style annotations. All uppercase, generous tracking. Never Fraunces. + +### Named Rules +**The Fraunces Hierarchy Rule.** Fraunces is exclusively for display, headline, and title roles. It is never used for body, label, or interface copy. Mixing the serif into UI text breaks the editorial/UI distinction the system depends on. + +**The Line Length Rule.** Body copy never exceeds 75 characters per line. On wide viewports, constrain with `max-w-[75ch]` or equivalent, not just container padding. + +## 4. Elevation + +This system is flat by default. Surfaces rest without shadow; the tonal neutral stack (parchment → cream → sand → butter) creates perceived depth through background steps, not drop shadows. + +Shadows appear in two specific contexts only: (1) interactive elements that need structural lift to signal they are "above" the page (logo frames in portfolio cards), and (2) the hero glass card overlay, which uses a semi-transparent white backdrop rather than a traditional shadow. + +### Shadow Vocabulary +- **Logo Frame Ambient** (`0 6px 20px rgba(0,0,0,0.06)`): The rotated Butter accent behind logo frames. Provides depth without weight. Never used on text containers. +- **Logo Frame Lift** (`0 8px 24px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.05)`): The logo card itself. Layered shadow creates subtle three-dimensional presence. + +### Named Rules +**The Flat-By-Default Rule.** A surface that doesn't move does not get a shadow. If adding a shadow is the first instinct, ask whether a background step (parchment → sand) solves the same problem without visual noise. + +## 5. Components + +### Buttons + +Clean pill form. The shape is soft but the intent is direct. Two meaningful variants; a third for inline text links. + +- **Shape:** Fully rounded pill (999px radius). Softness signals approachability, not weakness. +- **Primary:** Pomegranate background (`#AB4956`), White text (`#FFFFFF` — max contrast on brand surface), Outfit 500 label weight, 14px 20px padding. Hover lightens to Pomegranate Light (`#D35464`). Active state compresses to 98% scale. +- **Ghost:** Transparent background, Ink border and text, same pill shape and padding as Primary. Hover fills with `ink/5` (5% ink tint). Used for secondary actions alongside a Primary CTA. +- **Text:** No border, no background. Pomegranate text, no radius. Underlines on hover. Used inline in body copy or as a tertiary option. +- **Focus:** `outline-2 outline-offset-2 outline-brand` for all variants. Keyboard navigation must be visually unambiguous. + +### Cards / Containers + +The portfolio card is the signature container of this system. It is not a standard card grid — each card is a full-width editorial strip with intentional layout. + +- **Portfolio Card:** Sand background, Butter border (1px), 20px radius, internal padding steps (24px mobile / 40px tablet / 56px desktop). Two-column grid on desktop (text left, logo right). Cards stack with a scroll-scale depth effect on desktop. +- **Stats Cell:** Sand background, no border, no shadow. Stats live on the tonal surface without additional framing. +- **Logo Frame:** Butter rotated accent (–3deg) behind a white foreground frame. The rotation is the design element. Ambient shadow only. + +**The No Nested Card Rule.** Never place a card inside a card. The logo frame within a portfolio card is a logo display element, not a card — it has no interactive behavior and no content hierarchy of its own. + +### Navigation + +The `HomeLayout.astro` renders the page without a navigation bar. Future navigation should follow: Outfit label weight, Ink text, horizontal link list, no active-state underline, Pomegranate for a single CTA button (same primary button spec). + +### Signature Components + +**Portfolio Stack:** Six portfolio cards rendered as a sticky-scroll stack. On desktop, each card stacks behind the one above it using `position: sticky` with staggered `top` offsets (40px, 64px, 88px...) and a scroll-driven scale reduction (minimum 0.88). Cards enter with a `translateY(32px) → 0` fade. This is the design centerpiece of the homepage — do not simplify it to a carousel or grid. + +**Collage Cluster:** Four photos in a tight cluster with rotations (–8deg, +6deg, –5deg, +7deg), absolutely positioned to overlap a recessed stats grid. The collage is decorative evidence of community and events. Motion: ken-burns scale pulse (1→1.08→1, 14s, ease-in-out). Always `motion-reduce:animate-none`. + +**Motif Row:** Overlapping T4P logos with staggered fade-left entrance. Negative margin creates the overlap (`-space-x-[18px→22px→30px]`). Purely decorative; `aria-hidden`. + +## 6. Do's and Don'ts + +### Do: +- **Do** use Pomegranate (`#AB4956`) only on CTAs, brand emphasis, and active states. Its scarcity is its meaning. +- **Do** step through the neutral stack (Parchment → Cream → Sand → Butter) to create depth rather than adding shadows. +- **Do** use Fraunces exclusively for display, headline, and title roles. Outfit owns all body, label, and interface text. +- **Do** cap body text at 65–75ch line length on all viewport widths. +- **Do** guard every animation with `prefers-reduced-motion`. The CSS blanket `* { animation: none !important }` must be replaced with per-element guards. +- **Do** place CTAs where they are contextually earned — after evidence, after a clear invitation, not mechanically at the end of every section. +- **Do** use real faces, real event photos, and real project logos. Stock photography is prohibited. +- **Do** state T4P's mission explicitly and early. The cause is not implied; it is named. +- **Do** use the portfolio stack component (sticky scroll with depth) for featured projects. It is the homepage's signature interaction. + +### Don't: +- **Don't** use excessive interactivity or motion. Clarity of communication takes priority over visual spectacle. If an animation doesn't aid comprehension, remove it. +- **Don't** hedge or use vague "social good" language. T4P is explicitly pro-Palestine. Every headline must say so. +- **Don't** use cold or corporate visual language: blues, grays, geometric icons, or corporate-sans typography. This is a community, not a product. +- **Don't** use gradient text (`background-clip: text`). Use a single solid Pomegranate. Emphasis through weight or scale. +- **Don't** use side-stripe borders (`border-left > 1px` as a colored accent). Use full borders, background tints, or nothing. +- **Don't** repeat the hero metric pattern (big number, small label, supporting stats, gradient accent). The stats exist as proof points, not as a SaaS feature grid. +- **Don't** use glassmorphism as a decorative default. The hero glass card is purposeful (it must be legible over a full-bleed image). Everywhere else: solid tonal surfaces only. +- **Don't** use `#000000` or `#ffffff`. Every neutral is warm-tinted. +- **Don't** add a shadow to a surface that does not move or need structural lift. Flat surfaces stay flat. +- **Don't** use generic nonprofit design: blue-and-white palettes, stock photo heroes, "we believe in a better world" copy. diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 00000000..e75aa03a --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,57 @@ +# Product + +## Register + +brand + +## Users + +White-collar professionals, with an emphasis on tech workers (engineers, designers, founders, investors, lawyers). They already want to support Palestine but haven't found an outlet that matches their skillset. They arrive knowing they have a problem; T4P's job is to offer the clearest, most credible answer to "how do I help in a meaningful way?" + +## Product Purpose + +Tech for Palestine is a 501(c)(3) nonprofit that incubates and scales tech and advocacy projects for Palestinian liberation. The homepage is the front door to the movement. The primary conversion is **membership** (recurring donation + access to working committees). Secondary: tools downloads, incubator applications, volunteer sign-ups. + +Mission: "To harness technology and human innovation for a free and truly sovereign Palestine." + +Success in 3 months: measurable increase in membership sign-ups and tools downloads. + +## Brand Personality + +Warm, grounded, purposeful. T4P is honest and direct about its mission — there is no hedging, no ambiguity about what it stands for. It is human and community-forward, explicitly rejecting the norms of the tech establishment. It takes itself seriously without being heavy or institutional. + +Brand is often misunderstood because of a lack of cohesion so far — the design system launch is meant to fix this. + +## Anti-references + +- **Excessive complexity**: No over-engineered interactions, no motion for its own sake. "Less is more" is a core constraint. Accessibility and clarity of communication over visual spectacle. +- **Superficiality or hesitation**: T4P is explicitly pro-Palestine. That must be self-evident, not buried. No vague "social good" language. +- **Cold or corporate**: This is a community and a movement, not a startup or NGO. Nothing should feel like a pitch deck or a press release. +- Generic nonprofit sites: stock photo heroes, blue-and-white palettes, "we believe in a better world" copy. +- SaaS-style tech branding: metric dashboards, gradient blobs, glassmorphism cards. +- Advocacy sites that lead with despair: heavy, dark, guilt-driven. + +## Design Principles + +1. **Clarity first, craft second** — every design choice serves comprehension. If a visitor has to think about the layout, the layout has failed. +2. **Show the work, not the cause** — the 80+ real projects and their measurable impact carry the emotional argument. Stats and logos beat slogans. +3. **Human and warm** — use real faces, real community, real events. The palette (warm cream, pomegranate accent) should feel like a gathering space, not a product site. +4. **Earn each scroll** — every section must offer something new, not a re-skin of the pitch above it. Hierarchy: problem → evidence → invitation → action. +5. **Direct and unhesitating** — copy is mission-driven, specific, and never apologetic about what T4P stands for. + +## Accessibility & Inclusion + +Aim for WCAG AA in practice: color contrast, keyboard navigability, screen reader support. Scroll animations must respect `prefers-reduced-motion`. Complexity in interactions should be minimal by default (per brand anti-reference above). No formal certification target yet. + +## Key Stats (current, from brief) + +- 80+ supported projects +- 10,000+ Discord community of advocates +- 200+ pro-innovation builders (entrepreneurs) + +## Competitor References + +- Apricot International — faces, key stats, "how it works" clarity +- YCombinator — easy to read, range of projects +- 500.co — visual appeal, easy-to-read stats, project variety +- impacttoolbox.org — video with faces, project highlights diff --git a/astro.config.mjs b/astro.config.mjs index 0ef7898a..10bc3b48 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -11,6 +11,9 @@ import sentry from "@sentry/astro"; export default defineConfig({ site: "https://techforpalestine.org", output: "server", + prefetch: { + defaultStrategy: "hover", + }, adapter: cloudflare({ imageService: "cloudflare", }), @@ -63,8 +66,26 @@ export default defineConfig({ "/donate-2/", "/event-details/", "/404/", + "/about-new/", + "/team-new/", + "/faq-new/", + "/contact-new/", + "/projects-new/", + "/ideas-new/", + "/tools-new/", "/js/web.js/", "/admin/conversions/", + "/membership-new/", + "/volunteer-new/", + "/e4p-new/", + "/help/hire-new/", + "/mentorship-new/", + "/london-gathering-new/", + "/get-involved-new/", + "/media-new/", + "/legal-new/", + "/terms-new/", + "/privacy-policy-new/", ]; return !exclude.some((path) => page.endsWith(path)); }, diff --git a/package.json b/package.json index 15490a11..9c47b095 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "@astropub/icons": "^0.2.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", + "@fontsource/fraunces": "^5.2.9", + "@fontsource/outfit": "^5.2.8", "@iconify-json/simple-icons": "^1.2.54", "@mui/icons-material": "^6.5.0", "@mui/material": "^6.5.0", @@ -44,6 +46,7 @@ "chart.js": "^4.5.1", "install": "^0.13.0", "leaflet": "^1.9.4", + "lenis": "^1.3.23", "marked": "^15.0.12", "moderndash": "^3.12.0", "react": "^19.2.0", @@ -91,6 +94,7 @@ "@types/react-slick": "^0.23.13", "prettier": "^3.6.2", "prettier-plugin-astro": "^0.14.1", - "prettier-plugin-tailwindcss": "^0.7.1" + "prettier-plugin-tailwindcss": "^0.7.1", + "wrangler": "^4.85.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 947bb3d2..860da049 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,12 @@ importers: '@emotion/styled': specifier: ^11.14.1 version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@fontsource/fraunces': + specifier: ^5.2.9 + version: 5.2.9 + '@fontsource/outfit': + specifier: ^5.2.8 + version: 5.2.8 '@iconify-json/simple-icons': specifier: ^1.2.54 version: 1.2.54 @@ -108,6 +114,9 @@ importers: leaflet: specifier: ^1.9.4 version: 1.9.4 + lenis: + specifier: ^1.3.23 + version: 1.3.23(react@19.2.0) marked: specifier: ^15.0.12 version: 15.0.12 @@ -175,6 +184,9 @@ importers: prettier-plugin-tailwindcss: specifier: ^0.7.1 version: 0.7.1(prettier-plugin-astro@0.14.1)(prettier@3.6.2) + wrangler: + specifier: ^4.85.0 + version: 4.85.0(@cloudflare/workers-types@4.20251011.0) packages: @@ -960,6 +972,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.9.0 + '@fontsource/fraunces@5.2.9': + resolution: {integrity: sha512-XDzuddBtoC7BZgZdBn6b7hsFZY2+V1hgN7yca5fBTKuHjb/lOd45a0Ji8dTUgFhPoL7RdGupo+bC2BFSt6UH8Q==} + + '@fontsource/outfit@5.2.8': + resolution: {integrity: sha512-rXC6g0MqD7cOBjht0bMqc43qM6lRqDLML9KXsmg9uykz0wLQhy8Z/ajrMG6iyoT3NJR+MYgU+OEHp7uHoTb+Yw==} + '@iconify-json/bi@1.2.6': resolution: {integrity: sha512-fWfLr1/+DJDe8+rIUXxMwvmWBZFlxRtM59sYnrezJ2xX87QKyXVw3QuforJ4kF2Orrz85J+JTRG6305vaJ7flA==} @@ -2795,6 +2813,20 @@ packages: leaflet@1.9.4: resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} + lenis@1.3.23: + resolution: {integrity: sha512-YxYq3TJqj9sJNv0V9SkyQHejt14xwyIwgDaaMK89Uf9SxQfIszu+gTQSSphh6BWlLTNVKvvXAGkg+Zf+oFIevg==} + peerDependencies: + '@nuxt/kit': '>=3.0.0' + react: '>=17.0.0' + vue: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + react: + optional: true + vue: + optional: true + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -4937,6 +4969,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@fontsource/fraunces@5.2.9': {} + + '@fontsource/outfit@5.2.8': {} + '@iconify-json/bi@1.2.6': dependencies: '@iconify/types': 2.0.0 @@ -7053,6 +7089,10 @@ snapshots: leaflet@1.9.4: {} + lenis@1.3.23(react@19.2.0): + optionalDependencies: + react: 19.2.0 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} diff --git a/public/images/logo.avif b/public/images/logo.avif new file mode 100644 index 00000000..3a68deb1 Binary files /dev/null and b/public/images/logo.avif differ diff --git a/public/images/new-homepage/Media_Bias.webp b/public/images/new-homepage/Media_Bias.webp deleted file mode 100644 index ea1937f4..00000000 Binary files a/public/images/new-homepage/Media_Bias.webp and /dev/null differ diff --git a/public/images/new-homepage/a-ween-raeh.jpeg b/public/images/new-homepage/a-ween-raeh.jpeg deleted file mode 100644 index c70e7e7a..00000000 Binary files a/public/images/new-homepage/a-ween-raeh.jpeg and /dev/null differ diff --git a/public/images/new-homepage/hero/hero.webp b/public/images/new-homepage/hero/hero.webp new file mode 100644 index 00000000..675c1144 Binary files /dev/null and b/public/images/new-homepage/hero/hero.webp differ diff --git a/public/images/new-homepage/hero/t4p-logo.webp b/public/images/new-homepage/hero/t4p-logo.webp new file mode 100644 index 00000000..63cb55ba Binary files /dev/null and b/public/images/new-homepage/hero/t4p-logo.webp differ diff --git a/public/images/new-homepage/manifesto/who-are-we-1.webp b/public/images/new-homepage/manifesto/who-are-we-1.webp new file mode 100644 index 00000000..648a4d0a Binary files /dev/null and b/public/images/new-homepage/manifesto/who-are-we-1.webp differ diff --git a/public/images/new-homepage/manifesto/who-are-we-2.webp b/public/images/new-homepage/manifesto/who-are-we-2.webp new file mode 100644 index 00000000..b3970e9a Binary files /dev/null and b/public/images/new-homepage/manifesto/who-are-we-2.webp differ diff --git a/public/images/new-homepage/manifesto/who-are-we-3.webp b/public/images/new-homepage/manifesto/who-are-we-3.webp new file mode 100644 index 00000000..5138d769 Binary files /dev/null and b/public/images/new-homepage/manifesto/who-are-we-3.webp differ diff --git a/public/images/new-homepage/manifesto/who-are-we-4.webp b/public/images/new-homepage/manifesto/who-are-we-4.webp new file mode 100644 index 00000000..b0d0feb0 Binary files /dev/null and b/public/images/new-homepage/manifesto/who-are-we-4.webp differ diff --git a/public/images/new-homepage/portfolio/proj-logo-1.webp b/public/images/new-homepage/portfolio/proj-logo-1.webp new file mode 100644 index 00000000..989545a9 Binary files /dev/null and b/public/images/new-homepage/portfolio/proj-logo-1.webp differ diff --git a/public/images/new-homepage/portfolio/proj-logo-2.webp b/public/images/new-homepage/portfolio/proj-logo-2.webp new file mode 100644 index 00000000..3ef55bdb Binary files /dev/null and b/public/images/new-homepage/portfolio/proj-logo-2.webp differ diff --git a/public/images/new-homepage/portfolio/proj-logo-3.webp b/public/images/new-homepage/portfolio/proj-logo-3.webp new file mode 100644 index 00000000..59bbb398 Binary files /dev/null and b/public/images/new-homepage/portfolio/proj-logo-3.webp differ diff --git a/public/images/new-homepage/portfolio/proj-logo-4.webp b/public/images/new-homepage/portfolio/proj-logo-4.webp new file mode 100644 index 00000000..ea00cb4f Binary files /dev/null and b/public/images/new-homepage/portfolio/proj-logo-4.webp differ diff --git a/public/images/new-homepage/portfolio/proj-logo-5.webp b/public/images/new-homepage/portfolio/proj-logo-5.webp new file mode 100644 index 00000000..b691467b Binary files /dev/null and b/public/images/new-homepage/portfolio/proj-logo-5.webp differ diff --git a/public/images/new-homepage/portfolio/proj-logo-findaprotest.webp b/public/images/new-homepage/portfolio/proj-logo-findaprotest.webp new file mode 100644 index 00000000..6fd362dc Binary files /dev/null and b/public/images/new-homepage/portfolio/proj-logo-findaprotest.webp differ diff --git a/public/images/new-homepage/press/team-2.webp b/public/images/new-homepage/press/team-2.webp new file mode 100644 index 00000000..49fef871 Binary files /dev/null and b/public/images/new-homepage/press/team-2.webp differ diff --git a/public/images/new-homepage/support/team.webp b/public/images/new-homepage/support/team.webp new file mode 100644 index 00000000..ff939120 Binary files /dev/null and b/public/images/new-homepage/support/team.webp differ diff --git a/public/images/new-homepage/testimonials/hani.webp b/public/images/new-homepage/testimonials/hani.webp new file mode 100644 index 00000000..971b104b Binary files /dev/null and b/public/images/new-homepage/testimonials/hani.webp differ diff --git a/public/images/new-homepage/testimonials/issam-hijazi.webp b/public/images/new-homepage/testimonials/issam-hijazi.webp new file mode 100644 index 00000000..6b11144a Binary files /dev/null and b/public/images/new-homepage/testimonials/issam-hijazi.webp differ diff --git a/public/images/new-homepage/testimonials/newscord.webp b/public/images/new-homepage/testimonials/newscord.webp new file mode 100644 index 00000000..6f5b526a Binary files /dev/null and b/public/images/new-homepage/testimonials/newscord.webp differ diff --git a/public/images/new-homepage/testimonials/said.webp b/public/images/new-homepage/testimonials/said.webp new file mode 100644 index 00000000..044c02ea Binary files /dev/null and b/public/images/new-homepage/testimonials/said.webp differ diff --git a/public/images/new-homepage/why/paul.webp b/public/images/new-homepage/why/paul.webp new file mode 100644 index 00000000..022fa897 Binary files /dev/null and b/public/images/new-homepage/why/paul.webp differ diff --git a/public/logos/logo-dark-no-padding.webp b/public/logos/logo-dark-no-padding.webp new file mode 100644 index 00000000..f934ee83 Binary files /dev/null and b/public/logos/logo-dark-no-padding.webp differ diff --git a/public/logos/logo-dark.webp b/public/logos/logo-dark.webp new file mode 100644 index 00000000..7dd442ed Binary files /dev/null and b/public/logos/logo-dark.webp differ diff --git a/src/components/FAQSection.tsx b/src/components/FAQSection.tsx new file mode 100644 index 00000000..b9eefbf2 --- /dev/null +++ b/src/components/FAQSection.tsx @@ -0,0 +1,85 @@ +import React, { useState, useEffect } from "react"; +import RichTextRenderer from "./RichTextRenderer"; +import type { RichTextSegment } from "../types/richText"; + +interface FAQItem { + id: string; + question: string; + answer: string | RichTextSegment[]; +} + +function FAQAccordionItem({ question, answer }: { question: string; answer: FAQItem["answer"] }) { + const [open, setOpen] = useState(false); + + return ( +
+ +
+
+
+ {typeof answer === "string" ? ( +

{answer}

+ ) : Array.isArray(answer) ? ( + + ) : ( + answer + )} +
+
+
+
+ ); +} + +export default function FAQSection() { + const [faqs, setFaqs] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const showAll = new URLSearchParams(window.location.search).get("showAll") === "yes"; + fetch(showAll ? "/api/faq?showAll=yes" : "/api/faq", { cache: "no-cache" }) + .then((r) => (r.ok ? r.json() : [])) + .then((data) => setFaqs(data)) + .catch(() => setFaqs([])) + .finally(() => setLoading(false)); + }, []); + + if (loading) { + return ( +
+ Loading… +
+ ); + } + + if (faqs.length === 0) { + return ( +
+ No FAQs available at the moment. +
+ ); + } + + return ( +
+ {faqs.map((faq, index) => ( + + ))} +
+ ); +} diff --git a/src/components/IdeasWithTabsNew.tsx b/src/components/IdeasWithTabsNew.tsx new file mode 100644 index 00000000..7e0cff73 --- /dev/null +++ b/src/components/IdeasWithTabsNew.tsx @@ -0,0 +1,230 @@ +import React, { useState, useEffect, useRef } from "react"; +import { flushSync } from "react-dom"; +import RichTextRenderer from "./RichTextRenderer.tsx"; +import type { RichTextSegment, NotionRichText } from "../types/richText"; + +type Idea = { + id?: string; + slug?: string; + data: { + title: string; + tags?: string[]; + }; + richTextDescription?: RichTextSegment[] | NotionRichText | null; + excerpt?: string; +}; + +type IdeasWithTabsProps = { + newIdeas: Idea[]; + existingIdeas: Idea[]; + startedIdeas: Idea[]; +}; + +type ActiveIdeaType = { + title: string; + richTextDescription: RichTextSegment[] | NotionRichText; + tags: string[]; + isNew: boolean; +}; + +const TABS = [ + { key: "new" as const, label: "Ideas for new projects" }, + { key: "existing" as const, label: "Existing projects needing leaders" }, + { key: "started" as const, label: "Started projects" }, +]; + +export default function IdeasWithTabsNew({ + newIdeas, + existingIdeas, + startedIdeas, +}: IdeasWithTabsProps) { + const [activeTab, setActiveTab] = useState<"new" | "existing" | "started">("new"); + const [activeIdea, setActiveIdea] = useState(null); + const [modalVisible, setModalVisible] = useState(false); + const panelRef = useRef(null); + const closeTimer = useRef | null>(null); + + const MODAL_TRANSITION_MS = 300; + + const currentList = + activeTab === "new" ? newIdeas : activeTab === "existing" ? existingIdeas : startedIdeas; + + // ESC to close + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && activeIdea) closeModal(); + }; + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + }, [activeIdea]); + + // Freeze Lenis (smooth scroll) while modal is open so wheel events can't move the page. + useEffect(() => { + const lenis = (window as any).__lenis; + if (activeIdea) { + lenis?.stop(); + } else { + lenis?.start(); + } + return () => { lenis?.start(); }; + }, [activeIdea]); + + const openIdea = (idea: Idea) => { + if (closeTimer.current) clearTimeout(closeTimer.current); + // flushSync renders the modal at scale-95/opacity-0 before the rAF flips it + // visible, so the enter transition always plays (even on first open). + flushSync(() => + setActiveIdea({ + title: idea.data.title, + richTextDescription: + idea.richTextDescription && Array.isArray(idea.richTextDescription) + ? idea.richTextDescription + : idea.richTextDescription && "rich_text" in (idea.richTextDescription as object) + ? idea.richTextDescription + : [], + tags: idea.data.tags || [], + isNew: activeTab === "new", + }) + ); + requestAnimationFrame(() => setModalVisible(true)); + }; + + const closeModal = () => { + // Play the exit transition first, then unmount after it finishes. + setModalVisible(false); + closeTimer.current = setTimeout(() => setActiveIdea(null), MODAL_TRANSITION_MS); + }; + + // Clear any pending close timer on unmount + useEffect(() => () => { if (closeTimer.current) clearTimeout(closeTimer.current); }, []); + + return ( +
+
+ {/* Tabs */} +
+ {TABS.map(({ key, label }) => ( + + ))} +
+ + {/* Grid */} + {currentList.length === 0 ? ( +

No ideas in this category yet.

+ ) : ( +
+ {currentList.map((idea) => ( + + ))} +
+ )} +
+ + {/* Modal — always mounted once first idea is opened, controlled by modalVisible */} + {activeIdea && ( +
{ if (e.target === e.currentTarget) closeModal(); }} + className={[ + "fixed inset-0 z-50 flex items-center justify-center px-4 py-8", + "bg-ink/60 backdrop-blur-sm", + "transition-opacity duration-300 motion-reduce:transition-none", + modalVisible ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0", + ].join(" ")} + > +
+ + +

+ {activeIdea.isNew ? "New project idea" : "Project opportunity"} +

+

{activeIdea.title}

+ +
+ +
+ + {activeIdea.tags?.length > 0 && ( +
+ {activeIdea.tags.map((tag, i) => ( + + {tag} + + ))} +
+ )} + + +
+
+ )} +
+ ); +} diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro index c7d9ab01..8dc82c05 100644 --- a/src/components/Navigation.astro +++ b/src/components/Navigation.astro @@ -20,9 +20,8 @@ const navigation: Map = ({ richText, className = "" }) => { @@ -41,28 +40,16 @@ const RichTextRenderer: React.FC = ({ richText, className const flushCurrentList = () => { if (currentList) { - // Different bullet styles for different nesting levels - const listStyles = ["disc", "circle", "square"]; - const listStyleType = listStyles[currentList.indentLevel % listStyles.length]; - + const indentClass = currentList.indentLevel === 0 ? "" : currentList.indentLevel === 1 ? "ml-5" : "ml-10"; processedElements.push(
    {currentList.items.map((item, itemIndex) => (
  • {item}
  • @@ -141,9 +128,7 @@ const RichTextRenderer: React.FC = ({ richText, className processedElements.push(
    0 ? "8px" : "0", - }} + className={lineIndex > 0 ? "mt-2" : ""} > {lineContent}
    @@ -184,12 +169,11 @@ const RichTextRenderer: React.FC = ({ richText, className style.textDecoration = (style.textDecoration || "") + " underline"; } if (annotations.code) { - classes.push("font-mono bg-gray-100 px-1 py-0.5 rounded text-sm"); + classes.push("font-mono bg-sand px-1 py-0.5 rounded text-sm"); } if (annotations.color && annotations.color !== "default") { - // Map Notion colors to Tailwind/CSS classes const colorMap: Record = { - gray: "text-gray-600", + gray: "text-ink-secondary", brown: "text-amber-800", orange: "text-orange-600", yellow: "text-yellow-600", @@ -197,7 +181,7 @@ const RichTextRenderer: React.FC = ({ richText, className blue: "text-blue-600", purple: "text-purple-600", pink: "text-pink-600", - red: "text-red-600", + red: "text-brand", }; if (colorMap[annotations.color]) { classes.push(colorMap[annotations.color]); @@ -209,16 +193,16 @@ const RichTextRenderer: React.FC = ({ richText, className if (linkUrl) { return ( - {content} - + ); } @@ -231,9 +215,9 @@ const RichTextRenderer: React.FC = ({ richText, className }; return ( - +
    {processContent()} - +
    ); }; diff --git a/src/components/home/CtaSection.astro b/src/components/home/CtaSection.astro new file mode 100644 index 00000000..6a2b621d --- /dev/null +++ b/src/components/home/CtaSection.astro @@ -0,0 +1,125 @@ +--- +import Button from "../ui/Button.astro"; + +interface Props { + membershipLive?: boolean; +} + +const { membershipLive = false } = Astro.props; + +const amounts = [ + { label: "$25", href: "/donate?amount=25" }, + { label: "$50", href: "/donate?amount=50" }, + { label: "$100", href: "/donate?amount=100" }, + { label: "Custom", href: "/donate?amount=custom" }, +]; +--- + +
    +
    +
    + +
    +

    + You've seen what we've built.
    + Now build with us. +

    +

    + Join the coalition of professionals turning their skills into tools for liberation. The impact is real. +

    +

    + {membershipLive + ? "Join to support our mission and get access to our member Hub, where you can volunteer your skills, connect with the community and receive advanced updates." + : "Join to support our mission. Volunteer your skills, connect with the community and make an impact."} +

    +
    + {membershipLive + ? + : } +
    +
    + +
    +

    Can't join right now?

    +

    Donate to keep it running

    +

    + One-time or recurring. Your donation helps us scale existing projects and launch new supported initiatives for a free Palestine. +

    + +
    + {amounts.map((a) => ( + + {a.label} + + ))} +
    + +

    + T4P is a 501(c)(3). Donations are tax deductible in the US. +

    +

    + Click here to donate in the UK with Gift Aid. +

    +
    + +
    +
    +
    + + + + diff --git a/src/components/home/FooterSection.astro b/src/components/home/FooterSection.astro new file mode 100644 index 00000000..a9b38ee1 --- /dev/null +++ b/src/components/home/FooterSection.astro @@ -0,0 +1,147 @@ +--- +import { Icon } from "astro-icon/components"; + +interface Props { + membershipLive?: boolean; +} + +const { membershipLive = false } = Astro.props; + +const navigation: Record = { + About: [ + ["Our Organization", "/about-new"], + ["Team", "/team-new"], + ["FAQ", "/faq-new"], + ["Contact Us", "/contact-new"], + ], + Incubator: [ + ["Overview", "/incubator-new"], + ["Projects", "/projects-new"], + ["Ideas", "/ideas-new"], + ["Tools", "/tools-new"], + ], + "Get Involved": [ + ...(membershipLive ? [["Become a Member", "/membership"] as [string, string]] : []), + ["Donate", "/donate"], + ["Join the Incubator", "https://techforpalestine.org/project-application-form"], + ["Volunteer", "/volunteer-new"], + ["Entrepreneurs for Palestine", "/e4p-new"], + ["Be a Mentor", "/mentorship-new"], + ["Hire Palestinians", "/help/hire-new"], + ], + Events: [ + ["Past Events", "/events"], + ["Brussels 2026", "https://brussels2026.techforpalestine.org/"], + ["Barcelona 2026", "https://barcelona2026.techforpalestine.org/"], + ["Bay Area 2026", "https://summit.techforpalestine.org"], + ["London 2025", "/london-gathering"], + ["Updates", "https://updates.techforpalestine.org"], + ], +}; + +const socials: { name: string; url: string; icon: string }[] = [ + { name: "Instagram", url: "https://www.instagram.com/techforpalestine", icon: "mdi:instagram" }, + { name: "Twitter", url: "https://twitter.com/tech4palestine", icon: "mdi:twitter" }, + { name: "YouTube", url: "https://www.youtube.com/@tech4palestine", icon: "mdi:youtube" }, + { name: "GitHub", url: "https://github.com/TechForPalestine/", icon: "mdi:github" }, + { name: "LinkedIn", url: "https://www.linkedin.com/company/techforpalestine/", icon: "mdi:linkedin" }, + { name: "Discord", url: "https://techforpalestine.org/discord-invite", icon: "ic:baseline-discord" }, + { name: "Mastodon", url: "https://infosec.exchange/@tech4palestine", icon: "mdi:mastodon" }, + { name: "TikTok", url: "https://www.tiktok.com/@techforpalestine", icon: "ic:baseline-tiktok" }, + { name: "Bluesky", url: "https://bsky.app/profile/techforpalestine.org", icon: "simple-icons:bluesky" }, + { name: "UpScrolled", url: "https://share.upscrolled.com/en/user/39494e76-22a4-47ee-a67d-cd7049835a53/", icon: "/upscrolled-icon.svg" }, +]; +--- + + diff --git a/src/components/home/HeroSection.astro b/src/components/home/HeroSection.astro new file mode 100644 index 00000000..a6d9f113 --- /dev/null +++ b/src/components/home/HeroSection.astro @@ -0,0 +1,88 @@ +--- +import Button from "../ui/Button.astro"; + +interface Props { + membershipLive?: boolean; +} + +const { membershipLive = false } = Astro.props; +--- + +
    +
    + + Palestinian children playing near the separation wall + + + + + + +
    + + +
    +

    + Join the movement behind 90+ projects for Palestine +

    +
    +
    + +
    +
    + +
    +
    +
    +
    diff --git a/src/components/home/HomeNavbar.astro b/src/components/home/HomeNavbar.astro new file mode 100644 index 00000000..10acfd84 --- /dev/null +++ b/src/components/home/HomeNavbar.astro @@ -0,0 +1,443 @@ +--- +import Button from "../ui/Button.astro"; + +interface Props { + membershipLive?: boolean; + currentPath?: string; + alwaysVisible?: boolean; +} + +const { membershipLive = false, alwaysVisible = false } = Astro.props; +const currentPath = Astro.url.pathname; + +const navItems = [ + { + label: "About", + href: "/about-new", + children: [ + { label: "Our Organization", href: "/about-new" }, + { label: "Team", href: "/team-new" }, + { label: "FAQ", href: "/faq-new" }, + { label: "Contact Us", href: "/contact-new" }, + ], + }, + { + label: "Incubator", + href: "/incubator-new", + children: [ + { label: "Overview", href: "/incubator-new" }, + { label: "Projects", href: "/projects-new" }, + { label: "Ideas", href: "/ideas-new" }, + { label: "Tools", href: "/tools-new" }, + ], + }, + { + label: "Get Involved", + href: "/get-involved-new", + children: [ + ...(membershipLive ? [{ label: "Become a Member", href: "/membership" }] : []), + { label: "Donate", href: "/donate" }, + { label: "Join the Incubator", href: "https://techforpalestine.org/project-application-form" }, + { label: "Volunteer", href: "/volunteer-new" }, + { label: "Entrepreneurs for Palestine", href: "/e4p-new" }, + { label: "Be a Mentor", href: "/mentorship-new" }, + { label: "Hire Palestinians", href: "/help/hire-new" }, + { label: "Join our Discord", href: "https://techforpalestine.org/discord-invite" }, + ], + }, + { + label: "Events", + href: "/events", + children: [ + { label: "Past Events", href: "/events" }, + { label: "Brussels 2026", href: "https://brussels2026.techforpalestine.org/" }, + { label: "Barcelona 2026", href: "https://barcelona2026.techforpalestine.org/" }, + { label: "Bay Area 2026", href: "https://summit.techforpalestine.org" }, + { label: "London 2025", href: "/london-gathering" }, + ], + }, + { + label: "Updates", + href: "https://updates.techforpalestine.org", + }, +]; + +function isActive(itemHref: string, path: string): boolean { + if (itemHref.startsWith("http")) return false; + return path === itemHref || path.startsWith(itemHref + "/"); +} + +function isGroupActive(item: typeof navItems[number], path: string): boolean { + if (isActive(item.href, path)) return true; + return item.children?.some(c => isActive(c.href, path)) ?? false; +} +--- + + + + + + diff --git a/src/components/home/ManifestoSection.astro b/src/components/home/ManifestoSection.astro new file mode 100644 index 00000000..68e8930d --- /dev/null +++ b/src/components/home/ManifestoSection.astro @@ -0,0 +1,160 @@ +--- +const stats = [ + { value: "90", label: "Projects Incubated" }, + { value: "400", label: "Global Volunteers" }, + { value: "30", label: "Expert Mentors" }, + { value: "50", label: "Investors for Palestine" }, + { value: "350", label: "Entrepreneurs for Palestine" }, +]; + +const photos = [ + { + alt: "Tech for Palestine speaker at a Building Ethical Technology event", + src: "/images/new-homepage/manifesto/who-are-we-4.webp", + }, + { + alt: "Pro-Palestine protest outside a neoclassical government building", + src: "/images/new-homepage/manifesto/who-are-we-2.webp", + }, + { + alt: "Immigration lawyer holding flowers at a Tech for Palestine event", + src: "/images/new-homepage/manifesto/who-are-we-1.webp", + }, + { + alt: "Demonstrator in traditional Palestinian embroidery raising flowers at a street protest", + src: "/images/new-homepage/manifesto/who-are-we-3.webp", + }, +]; +--- + +
    +
    + + + + +

    + Thousands of founders, tech workers, business professionals and advocates building tools and products that protect, inform, resist, and rebuild. +

    +
    + + +
    +
    + {stats.map((s) => ( +
    +
    + +
    +
    {s.label}
    +
    + ))} +
    + + +
    +
    + {photos.map((p, i) => ( + + ))} +
    +
    +
    +
    + + diff --git a/src/components/home/OpeningsSection.astro b/src/components/home/OpeningsSection.astro new file mode 100644 index 00000000..6fe0049a --- /dev/null +++ b/src/components/home/OpeningsSection.astro @@ -0,0 +1,226 @@ +--- +import Button from "../ui/Button.astro"; + +interface Role { + id: string; + title: string; + description: string | null; + skillCategories: string[]; + timeCommitment: string | null; + type: "team" | "project"; + team: { name: string } | null; + project: { id: string; name: string } | null; +} + +function roleName(role: Role): string { + if (role.type === "team") return role.team?.name ?? "T4P"; + const parts = role.title.split(" for "); + if (parts.length > 1) return parts[parts.length - 1]; + return role.title; +} + +function roleTime(role: Role): string | null { + return role.timeCommitment ?? role.description ?? null; +} + +interface Props { + membershipLive?: boolean; + roles: Role[]; +} + +const { membershipLive = false, roles } = Astro.props; +const visible = roles.slice(0, 4); + +function applyHref(role: Role): string { + if (role.type === "project") return "/volunteer"; + return membershipLive ? `/membership?targetRoleId=${role.id}` : "/get-involved-new"; +} +--- + +
    +
    + +
    +
    +

    Current openings

    +

    + Teams & projects waiting for their next volunteer +

    +
    +

    + {membershipLive + ? "This could be you. Become a member to fill one of these impactful openings." + : "This could be you. Get involved to fill one of these impactful openings."} +

    +
    + +
    + +
      + {visible.map((role) => ( +
    • +
      +

      {roleName(role)}

      +
      +

      {role.title}

      + {role.skillCategories.length > 0 && ( +

      {role.skillCategories.join(" · ")}

      + )} +
      + {roleTime(role) && ( +

      {roleTime(role)}

      + )} + + Apply + + +
      +
    • + ))} +
    + +
    + +
    + +
    +
    + + + + diff --git a/src/components/home/PortfolioSection.astro b/src/components/home/PortfolioSection.astro new file mode 100644 index 00000000..7d47afe3 --- /dev/null +++ b/src/components/home/PortfolioSection.astro @@ -0,0 +1,297 @@ +--- +const LOGO_DEFAULTS = { + bg: "bg-white", + padding: "p-8 md:p-4 lg:p-8", + imgClass: "max-h-[170px] w-full max-w-[340px] object-contain", +}; + +const projects = [ + { + name: "UpScrolled", + url: "https://upscrolled.com/en/", + description: + "Meta and other social media apps are controlled by billionaires and suppress pro-Palestine content. This social platform has no shadowbans and no algorithmic games. Launched and scaled via the T4P incubator and ecosystem partners.", + logo: { src: "/images/new-homepage/portfolio/proj-logo-5.webp", alt: "UpScrolled logo" }, + stats: [ + { label: "in App Store", value: "#1" }, + { label: "users", value: "5M+" }, + ], + }, + { + name: "Boycat", + url: "https://www.boycat.io/", + description: + "Makes boycotting easy. Scan any product to check its ethical compliance and find alternatives to products complicit in the genocide in real time. Official BDS partner app, connected through T4P.", + logo: { src: "/images/new-homepage/portfolio/proj-logo-3.webp", alt: "Boycat logo" }, + stats: [ + { label: "users", value: "3M+" }, + { label: "redirected to ethical brands", value: "$500M+" }, + ], + }, + { + name: "Find a Protest", + url: "https://www.findaprotest.info/", + description: + "Find and subscribe to protests for Palestine in your area, powered by organizers, communities, and volunteer data collectors. Powered by volunteers & strategic guidance through T4P.", + logo: { + src: "/images/new-homepage/portfolio/proj-logo-findaprotest.webp", + alt: "Find a Protest logo", + imgClass: "max-h-[120px] w-full max-w-[220px] object-contain", + }, + stats: [ + { label: "monthly users", value: "200k+" }, + { label: "protests & solidarity events listed", value: "10k+" }, + ], + }, + { + name: "Thaura.ai", + url: "https://thaura.ai/home", + description: + "Mainstream LLM models are designed with imperialist and Zionist bias. Thaura is AI built ethically, pro-Palestine, designed around environmental, social, and privacy concerns. Launched via the T4P network to profitability.", + logo: { src: "/images/new-homepage/portfolio/proj-logo-4.webp", alt: "Thaura.ai logo" }, + stats: [ + { label: "less energy than ChatGPT per query", value: "94%" }, + { label: "data sold to third parties", value: "0" }, + ], + }, + { + name: "Newscord", + url: "https://newscord.org/", + description: + "Exposes media bias against Palestine with AI-powered news summaries across hundreds of sources, and tracking of biased headlines & responsible news outlets. T4P marketing support & connections to ecosystem partners.", + logo: { + src: "/images/new-homepage/portfolio/proj-logo-1.webp", + alt: "Newscord logo", + bg: "bg-black", + padding: "px-14 py-0", + imgClass: "max-h-[170px] w-[180px] max-w-full object-contain", + }, + stats: [{ label: "accounts reached monthly", value: "10M+" }], + }, + { + name: "Apricot", + url: "https://apricotinternational.org/", + description: + "Connecting top tech talent from the Middle East, including Palestinian refugees, with US tech companies. Customer introductions and strategic advice via T4P.", + logo: { src: "/images/new-homepage/portfolio/proj-logo-2.webp", alt: "Apricot logo" }, + stats: [ + { label: "paid to placed talent", value: "$1M+" }, + { label: "candidates", value: "10k+" }, + ], + }, +]; +--- + +
    +
    +

    Our portfolio

    + +
    +

    + Projects that came through T4P +

    +
    +

    + Each of these projects was scaled through T4P. Powered by volunteers, mentors, and people like you. +

    +

    + If you have a business or nonprofit that supports Palestine, Apply to the Incubator. +

    +
    +
    + +
    +
    + +
    +
      + {projects.map((p, i) => ( +
    • +
      +
      +

      {p.name}

      +

      + {p.description} +

      +
      + {p.stats.map((s) => ( +
      +
      {s.label}
      +
      + {s.value} +
      +
      + ))} +
      + {p.url && ( + + Visit {p.name} + + + )} +
      + +
      + +
      + {p.logo.alt} +
      +
      +
      +
    • + ))} +
    +
    +
    + + + + diff --git a/src/components/home/PressSection.astro b/src/components/home/PressSection.astro new file mode 100644 index 00000000..14387823 --- /dev/null +++ b/src/components/home/PressSection.astro @@ -0,0 +1,251 @@ +--- +const logos = [ + { src: "https://upload.wikimedia.org/wikipedia/commons/7/75/The_Guardian_2018.svg", alt: "The Guardian" }, + { src: "https://e7.pngegg.com/pngimages/669/74/png-clipart-tc-techcrunch-logo-illustration-techcrunch-logo-icons-logos-emojis-iconic-brands.png", alt: "TechCrunch" }, + { src: "https://upload.wikimedia.org/wikipedia/en/thumb/8/8f/Al_Jazeera_Media_Network_Logo.svg/1280px-Al_Jazeera_Media_Network_Logo.svg.png", alt: "Al Jazeera" }, + { src: "https://upload.wikimedia.org/wikipedia/commons/d/d6/Inc._%28business_magazine%29_logo.svg", alt: "Inc." }, + { src: "https://ssir.org/images/resources/SSIR_Logo.RGB_Black_984x234.png", alt: "Stanford Social Innovation Review"}, + { src: "https://upload.wikimedia.org/wikipedia/commons/4/4f/TRT_World_logosu.png", alt: "TRT World" }, +]; + +const articles = [ + { + publication: "Stanford Social Innovation Review", + date: "2024", + headline: "Tech for Palestine Business Incubator Supports Entrepreneurs Protecting Human Rights", + url: "https://ssir.org/articles/entry/tech-against-genocide", + }, + { + publication: "TechCrunch", + date: "Jan 2024", + headline: "Tech for Palestine launches to provide tools to help support Palestinians", + url: "https://techcrunch.com/2024/01/02/tech-for-palestine-launches-to-provide-tools-and-projects-to-help-advocate-for-palestinians/", + }, + { + publication: "Al Jazeera", + date: "Jan 2026", + headline: "What's UpScrolled, the app gaining popularity after TikTok's US takeover?", + url: "https://www.aljazeera.com/news/2026/1/29/whats-upscrolled-the-app-gaining-popularity-after-tiktoks-us-takeover", + }, + { + publication: "The Guardian", + date: "Nov 2025", + headline: "Fundraisers warn of 'catastrophic' drop in donations to Gaza since ceasefire", + url: "https://www.theguardian.com/global-development/2025/nov/21/fundraisers-warn-catastrophic-drop-donations-aid-palestinians-gaza-israel-ceasefire", + }, + { + publication: "TRT World", + date: "2024", + headline: "Tech for Palestine initiative supports advocacy and accountability amid Israel's use of AI", + url: "https://www.trtworld.com/video/18268907", + }, + { + publication: "Counter Points", + date: "2024", + headline: "Paul on Counter Points, Tech for Palestine", + url: "https://www.youtube.com/watch?v=UldcFPEZBM4&t=4388s", + }, +]; +--- + +
    +
    + +

    In the press

    +

    Making headlines

    + + +
    +
      + {logos.map((logo) => ( +
    • + {logo.alt} +
    • + ))} +
    +
    + + + + + +
    + Tech for Palestine community gathered at a workshop and strategy session +
    + +
    +
    + + + + diff --git a/src/components/home/SupportSection.astro b/src/components/home/SupportSection.astro new file mode 100644 index 00000000..41c3fd0d --- /dev/null +++ b/src/components/home/SupportSection.astro @@ -0,0 +1,161 @@ +--- +const items = [ + { + number: "01", + heading: "Mentorship", + body: "Strategy sessions and optional weekly office hours. Real guidance from people who've built, shipped, and scaled before.", + label: "30+ expert mentors · async + live", + }, + { + number: "02", + heading: "Community", + body: "Access to a global network of skilled members: engineers, marketers, designers and more, with a dedicated recruitment team to match you to the people you need. Connections to NGOs, advocacy groups, civil society organizations, influencers, and funders.", + label: "400+ global volunteers", + }, + { + number: "03", + heading: "Operations", + body: "Engineering expertise, security support, marketing campaigns and early users through press, influencers, ecosystem partners, and the T4P community. We help projects reach the people who need them.", + label: "Tailored for your needs", + }, + { + number: "04", + heading: "Funding", + body: "Small grants for most accepted projects. Simple process, no strings attached. We fund what works, not what looks good on a deck.", + label: "Rolling basis", + }, +]; +--- + +
    +
    +

    Our support

    + +

    + How do we enable projects to succeed? +

    +

    + We give teams and projects the support they need to ship, scale, and make an impact. +

    + +
    + +
    + Tech for Palestine community gathered at the Barcelona conference, April 2026 +
    + +
      + {items.map((item, i) => ( +
    • + {item.number} +
      +

      {item.heading}

      +

      {item.body}

      +

      {item.label}

      +
      +
    • + ))} +
    +
    +
    + + + + diff --git a/src/components/home/TestimonialsSection.astro b/src/components/home/TestimonialsSection.astro new file mode 100644 index 00000000..642c8b55 --- /dev/null +++ b/src/components/home/TestimonialsSection.astro @@ -0,0 +1,300 @@ +--- +const testimonials = [ + { + quote: + "The valuable advice and launch support from T4P was exactly what we needed to finally hit cash-flow positive and build our impact.", + name: "Hani & Said Chihabi", + role: "Founders, Thaura", + logo: "/thaura.svg", + headshots: [ + "/images/new-homepage/testimonials/hani.webp", + "/images/new-homepage/testimonials/said.webp", + ], + }, + { + quote: + "Tech for Palestine has been a true partner to UpScrolled from the early days, opening doors to their ecosystem and standing by us at every step of the journey.", + name: "Issam Hijazi", + role: "Founder, UpScrolled", + logo: "/upscrolled-icon.svg", + headshot: "/images/new-homepage/testimonials/issam-hijazi.webp", + }, + { + quote: + "With the volunteers and partnerships provided, we went from a minimally viable concept to a full-fledged global platform reaching millions of accounts each month.", + name: "Nima Akram", + role: "Founder, Newscord", + logo: "/newscord.svg", + headshot: "/images/new-homepage/testimonials/newscord.webp", + }, +]; +--- + +
    +
    + +
    +
    +

    From the people building with us

    +

    + What it's like to work with T4P +

    +
    +

    + Project leaders, mentors, and members on their time inside the coalition. +

    +
    + +
    + +
    +
    + {[...testimonials, ...testimonials, ...testimonials].map((t, i) => ( +
    = testimonials.length && "marquee-dupe"]}> + +

    {t.quote}

    +
    + {t.headshots ? ( +
    + + +
    + ) : ( + + )} +
    +

    {t.name}

    +

    {t.role}

    +
    +
    +
    + ))} +
    +
    + +
    +
    + + + + diff --git a/src/components/home/WhySection.astro b/src/components/home/WhySection.astro new file mode 100644 index 00000000..49bd64b3 --- /dev/null +++ b/src/components/home/WhySection.astro @@ -0,0 +1,111 @@ +--- +--- + +
    +
    +
    + + +
    +

    Paul & Greta on Palestine and media bias

    +
    + +
    +
    + + +
    +
    +

    + "Tens of thousands of well-funded hasbara projects exist. We're building tens of thousands of liberation initiatives to match them." +

    +
    +
    + Paul Biggar, Founder of Tech for Palestine +
    +

    Paul Biggar

    +

    Founder, Tech for Palestine

    +
    +
    +
    + +
    +
    +
    + + + + diff --git a/src/components/london-gathering/AgendaSection.tsx b/src/components/london-gathering/AgendaSection.tsx new file mode 100644 index 00000000..e2803c7e --- /dev/null +++ b/src/components/london-gathering/AgendaSection.tsx @@ -0,0 +1,214 @@ +import { useState, useEffect } from "react"; + +interface Moderator { + id: string; + name: string; + title: string; + bio: string; + photo: string; +} + +interface AgendaItem { + id: string; + title: string; + description: string; + time: string; + moderator: Moderator | null; +} + +function sortAgendaItems(items: AgendaItem[]): AgendaItem[] { + const getBreakoutNumber = (title: string) => { + const match = title.match(/Breakout (\d+)/); + return match ? parseInt(match[1]) : 0; + }; + + const session1Breakouts = items + .filter((item) => item.title.includes("Breakout") && item.time === "10:30 - 12:00") + .sort((a, b) => getBreakoutNumber(a.title) - getBreakoutNumber(b.title)); + + const session2Breakouts = items + .filter((item) => item.title.includes("Breakout") && item.time === "13:30 - 15:00") + .sort((a, b) => getBreakoutNumber(a.title) - getBreakoutNumber(b.title)); + + const otherItems = items.filter((item) => !item.title.includes("Breakout")); + + const sorted = otherItems.sort((a, b) => { + const getStartMinutes = (timeStr: string) => { + if (!timeStr) return 0; + const startTime = timeStr.split(" - ")[0]; + const match = startTime.match(/^(\d{1,2}):(\d{2})/); + if (!match) return 0; + return parseInt(match[1], 10) * 60 + parseInt(match[2], 10); + }; + return getStartMinutes(a.time) - getStartMinutes(b.time); + }); + + const result: AgendaItem[] = []; + let session1Added = false; + sorted.forEach((item) => { + result.push(item); + if (!session1Added && item.title.toLowerCase().includes("keynote")) { + result.push({ + id: "session1-header", + title: "Workshop Session 1", + description: "", + time: "10:30 - 12:00", + moderator: null, + }); + result.push(...session1Breakouts); + session1Added = true; + } else if (item.title.toLowerCase().includes("lunch")) { + result.push({ + id: "session2-header", + title: "Workshop Session 2", + description: "", + time: "13:30 - 15:00", + moderator: null, + }); + result.push(...session2Breakouts); + } + }); + + return result; +} + +function AgendaIcon({ title }: { title: string }) { + const t = title.toLowerCase(); + if (t.includes("coffee") || t.includes("welcome") || t.includes("goodbye") || t.includes("closing")) { + return ( + + + + ); + } + if (t.includes("lunch")) { + return ( + + + + ); + } + if (t.includes("breakout") || t.includes("workshop")) { + return ( + + + + ); + } + return ( + + + + ); +} + +function getIconBg(title: string): string { + const t = title.toLowerCase(); + if (t.includes("coffee") || t.includes("welcome") || t.includes("goodbye") || t.includes("closing")) + return "bg-brand"; + if (t.includes("lunch")) return "bg-[#d97706]"; + if (t.includes("breakout") || t.includes("workshop")) return "bg-ink-dark"; + return "bg-[#168039]"; +} + +interface AgendaSectionProps { + initialAgendaItems?: AgendaItem[]; +} + +export default function AgendaSection({ initialAgendaItems = [] }: AgendaSectionProps) { + const [agendaItems, setAgendaItems] = useState( + initialAgendaItems.length > 0 ? sortAgendaItems(initialAgendaItems) : [] + ); + const [loading, setLoading] = useState(initialAgendaItems.length === 0); + + useEffect(() => { + if (initialAgendaItems.length === 0) { + fetch("/api/speakers") + .then((r) => r.json()) + .then((data) => setAgendaItems(sortAgendaItems(data.agendaItems || []))) + .catch((err) => console.error("Error fetching agenda:", err)) + .finally(() => setLoading(false)); + } + }, []); + + if (loading) { + return ( +
    +
    +

    Loading agenda...

    +
    +
    + ); + } + + return ( +
    +
    +

    Schedule

    +

    Agenda

    + +
    + {agendaItems.map((item) => { + const isWorkshopHeader = item.title.includes("Workshop Session"); + const isBreakout = item.title.includes("Breakout"); + + if (isWorkshopHeader) { + return ( +
    +
    +
    + +
    +
    +

    {item.time}

    +

    + {item.title} +

    +
    +
    +
    + ); + } + + if (isBreakout) { + return ( +
    +
    +

    + {item.title} + {item.moderator && ( + + {" "}(moderated by {item.moderator.name}) + + )} +

    +
    +
    + ); + } + + return ( +
    +
    +
    + +
    +
    +

    {item.time}

    +

    {item.title}

    +
    +
    +
    + ); + })} +
    +
    +
    + ); +} diff --git a/src/components/london-gathering/SpeakersSection.tsx b/src/components/london-gathering/SpeakersSection.tsx new file mode 100644 index 00000000..9329cef7 --- /dev/null +++ b/src/components/london-gathering/SpeakersSection.tsx @@ -0,0 +1,116 @@ +import { useState, useEffect, useRef, useCallback } from "react"; + +interface Speaker { + id: string; + name: string; + title: string; + bio: string; + photo: string; +} + +interface SpeakersSectionProps { + initialSpeakers?: Speaker[]; +} + +export default function SpeakersSection({ initialSpeakers = [] }: SpeakersSectionProps) { + const [speakers, setSpeakers] = useState(initialSpeakers); + const [loading, setLoading] = useState(initialSpeakers.length === 0); + const [selectedSpeaker, setSelectedSpeaker] = useState(null); + const dialogRef = useRef(null); + + useEffect(() => { + if (initialSpeakers.length === 0) { + fetch("/api/speakers") + .then((r) => r.json()) + .then((data) => setSpeakers(data.speakers || [])) + .catch((err) => console.error("Error fetching speakers:", err)) + .finally(() => setLoading(false)); + } + }, []); + + const openModal = useCallback((speaker: Speaker) => { + setSelectedSpeaker(speaker); + dialogRef.current?.showModal(); + }, []); + + const closeModal = useCallback(() => { + dialogRef.current?.close(); + }, []); + + if (loading) { + return ( +
    +
    +

    Loading speakers...

    +
    +
    + ); + } + + return ( +
    +
    +

    Who spoke

    +

    Speakers

    + +
    + {speakers.map((speaker) => ( + + ))} +
    +
    + + { + if (e.target === dialogRef.current) closeModal(); + }} + > + {selectedSpeaker && ( +
    +
    + {selectedSpeaker.name} +
    +

    {selectedSpeaker.name}

    +

    {selectedSpeaker.title}

    +
    + +
    +
    +

    + {selectedSpeaker.bio || "Bio coming soon..."} +

    +
    +
    + )} +
    +
    + ); +} diff --git a/src/components/membership/MembershipCalculator.tsx b/src/components/membership/MembershipCalculator.tsx new file mode 100644 index 00000000..d8f6f28f --- /dev/null +++ b/src/components/membership/MembershipCalculator.tsx @@ -0,0 +1,151 @@ +import { useState } from "react"; + +const CURRENCIES = [ + { code: "USD", symbol: "$", name: "US Dollar", usdRate: 1 }, + { code: "EUR", symbol: "€", name: "Euro", usdRate: 1.08 }, + { code: "GBP", symbol: "£", name: "British Pound", usdRate: 1.27 }, + { code: "CAD", symbol: "C$", name: "Canadian Dollar", usdRate: 0.74 }, + { code: "AUD", symbol: "A$", name: "Australian Dollar", usdRate: 0.65 }, + { code: "JPY", symbol: "¥", name: "Japanese Yen", usdRate: 0.0067 }, + { code: "CHF", symbol: "CHF", name: "Swiss Franc", usdRate: 1.12 }, + { code: "CNY", symbol: "¥", name: "Chinese Yuan", usdRate: 0.14 }, + { code: "INR", symbol: "₹", name: "Indian Rupee", usdRate: 0.012 }, + { code: "BRL", symbol: "R$", name: "Brazilian Real", usdRate: 0.19 }, + { code: "MXN", symbol: "MX$", name: "Mexican Peso", usdRate: 0.057 }, + { code: "ZAR", symbol: "R", name: "South African Rand", usdRate: 0.055 }, +]; + +export default function MembershipCalculator() { + const [incomeType, setIncomeType] = useState<"annual" | "monthly">("monthly"); + const [income, setIncome] = useState(""); + const [currency, setCurrency] = useState("USD"); + + const currencyData = CURRENCIES.find((c) => c.code === currency) ?? CURRENCIES[0]; + const isUSD = currency === "USD"; + const numericIncome = parseFloat(income); + const hasValidIncome = !isNaN(numericIncome) && numericIncome > 0; + const monthlyIncome = hasValidIncome + ? incomeType === "monthly" + ? numericIncome + : numericIncome / 12 + : 0; + const suggestedMonthly = Math.round(monthlyIncome * 0.006 * 100) / 100; + const suggestedAnnual = Math.round(suggestedMonthly * 12 * 100) / 100; + + return ( +
    + {/* Header */} +

    Calculate your suggested dues

    + + {/* Inputs — single row on desktop */} +
    + {/* Currency */} +
    + +
    + + + + + + +
    +
    + + {/* Period toggle */} +
    +

    Period

    +
    + {(["monthly", "annual"] as const).map((type) => ( + + ))} +
    +
    + + {/* Amount */} +
    + +
    + + {currencyData.symbol} + + setIncome(e.target.value)} + placeholder={incomeType === "annual" ? "60 000" : "5 000"} + className="w-full rounded-lg border border-butter bg-sand py-2.5 pl-7 pr-3 text-sm text-ink placeholder:text-ink-muted focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand" + /> +
    +
    +
    + + {/* Results */} + {hasValidIncome ? ( +
    +
    +

    Monthly dues

    +

    + {currencyData.symbol}{suggestedMonthly.toFixed(2)} +

    + {!isUSD && ( +

    + ~${Math.round(suggestedMonthly * currencyData.usdRate)} USD +

    + )} +
    +
    +

    Annual dues

    +

    + {currencyData.symbol}{suggestedAnnual.toFixed(2)} +

    + {!isUSD && ( +

    + ~${Math.round(suggestedAnnual * currencyData.usdRate)} USD +

    + )} +
    +
    + ) : ( +

    + Enter your income above to see a suggested amount. +

    + )} +
    + ); +} diff --git a/src/components/membership/MembershipDues.tsx b/src/components/membership/MembershipDues.tsx new file mode 100644 index 00000000..e9b9f491 --- /dev/null +++ b/src/components/membership/MembershipDues.tsx @@ -0,0 +1,139 @@ +import { useEffect, useRef, useState } from "react"; +import MembershipCalculator from "./MembershipCalculator"; + + +function QgivEmbed() { + const scriptLoadedRef = useRef(false); + + useEffect(() => { + if (scriptLoadedRef.current) return; + scriptLoadedRef.current = true; + + const script = document.createElement("script"); + script.src = "https://secure.qgiv.com/resources/core/js/embed.js"; + script.id = "qgiv-embedjs"; + script.async = true; + document.body.appendChild(script); + }, []); + + return ( +
    +
    +
    + ); +} + +export default function MembershipDues() { + const [showCalculator] = useState(() => { + const urlParams = new URLSearchParams(window.location.search); + const urlParam = urlParams.get("calculator"); + if (urlParam === "yes") return true; + if (urlParam === "no") return false; + + const stored = localStorage.getItem("membership_ab_variant"); + if (stored === "Calculator") return true; + if (stored === "No Calculator") return false; + + const assigned = Math.random() < 0.5; + localStorage.setItem( + "membership_ab_variant", + assigned ? "Calculator" : "No Calculator" + ); + return assigned; + }); + + useEffect(() => { + if (typeof window.plausible !== "undefined") { + window.plausible("Membership Page", { + props: { + membership_variant: showCalculator ? "Calculator" : "No Calculator", + }, + }); + } + }, [showCalculator]); + + return ( +
    + {/* Intro copy — left-aligned with the section heading */} +

    + {showCalculator + ? "Contribute any amount for membership dues. We suggest monthly dues equal to one hour's salary, which you can calculate below:" + : "Contribute any amount for membership dues. We suggest monthly dues equal to one hour's salary."} +

    + + {/* Calculator + form — constrained width, left-aligned */} +
    + {showCalculator && } + + {/* Form + side info */} +
    + + + {/* Side info */} +
    +
    +

    Inclusivity & waivers

    +

    + Tech for Palestine aims for inclusivity. Please contact{" "} + + membership@techforpalestine.org + {" "} + to request a waiver of dues in the following circumstances: +

    +
      + {[ + "Not having access to banking services/debit card", + "Being located in Gaza or the West Bank", + "Being a refugee from Gaza or the West Bank evacuated during the genocide", + "Not being able to afford membership due to personal circumstances", + "Being a T4P paid staff member", + ].map((item) => ( +
    • +
    • + ))} +
    +

    + If you are in the US, your dues are tax deductible. If you are in the UK, contact us at{" "} + + membership@techforpalestine.org + {" "} + after signup and we will ensure that future donations are processed through our gift aid partner. +

    +

    + Options to pay via DAF, cryptocurrency, foundations, and other methods will be supported in the future. We will help you migrate to your preferred method of giving once available. +

    +
    + +
    +

    Get in touch

    +

    + If you have questions, set up an{" "} + + intro call + {" "} + or reach out to us at{" "} + + membership@techforpalestine.org + + ! +

    +
    +
    +
    +
    +
    + ); +} diff --git a/src/components/membership/MembershipFAQSection.tsx b/src/components/membership/MembershipFAQSection.tsx new file mode 100644 index 00000000..f76faef8 --- /dev/null +++ b/src/components/membership/MembershipFAQSection.tsx @@ -0,0 +1,124 @@ +import { useState } from "react"; + +interface FAQItem { + question: string; + answer: string | { text: string; items?: string[] }; +} + +const faqs: FAQItem[] = [ + { + question: "How does membership support Palestinian liberation?", + answer: + "Membership brings together like-minded members and activists to lead, co-lead or support projects and committees. Members can support the movement by contributing financially or by getting involved directly as a working committee leader or member.", + }, + { + question: "How do I get involved after signup?", + answer: { + text: "We will send you an email about the ways to get involved as a member:", + items: [ + "Get T4P support on leveling up personal and corporate boycotts, using ethical alternatives, and learning how to activate against tech complicity.", + "Bring T4P resources into your companies for hiring and policy support.", + "Meet with us one-on-one to see how your skills and interests can support the mission.", + "Join working committees of fellow advocates taking direct action for Palestinian liberation.", + "Attend meetups and webinars to connect and learn from other activists.", + "Stay up-to-date by reading and sharing movement updates.", + ], + }, + }, + { + question: "Why are there membership dues?", + answer: + "Dues are pay-what-you-can and help us progress towards our goal of 10,000 projects for Palestine by supporting project work itself — grants for advocacy projects, access to expert trainings, mentorship from startup founders, and other developmental opportunities to help projects scale — and by supporting the infrastructure that lets us get work done, including software and employees. Tech for Palestine aims for inclusivity. If you are unable to access banking services, please reach out to membership@techforpalestine.org to request a waiver of dues.", + }, + { + question: "Are dues tax deductible? Can I apply gift aid?", + answer: + "Yes, if you are in the US your dues are tax deductible. If you are in the UK, contact us at membership@techforpalestine.org after signing up, and we will ensure future donations are processed through our gift aid partner.", + }, + { + question: + "Can I pay annually or via a different payment method (DAF, cryptocurrency, foundation, etc.)?", + answer: + "These options will be supported in the future, and we will help you migrate to your preferred method of giving once available.", + }, +]; + +function linkifyEmail(text: string) { + const parts = text.split("membership@techforpalestine.org"); + return parts.map((part, i) => + i < parts.length - 1 ? ( + + {part} + + membership@techforpalestine.org + + + ) : ( + {part} + ) + ); +} + +function FAQItem({ question, answer }: FAQItem) { + const [open, setOpen] = useState(false); + + return ( +
    + +
    +
    +
    + {typeof answer === "string" ? ( +

    {linkifyEmail(answer)}

    + ) : ( + <> +

    {answer.text}

    + {answer.items && ( +
      + {answer.items.map((item) => ( +
    • +
    • + ))} +
    + )} + + )} +
    +
    +
    +
    + ); +} + +export default function MembershipFAQSection() { + return ( +
    + {faqs.map((faq) => ( + + ))} +
    + ); +} diff --git a/src/components/projects/ProjectCard.tsx b/src/components/projects/ProjectCard.tsx new file mode 100644 index 00000000..92eb27af --- /dev/null +++ b/src/components/projects/ProjectCard.tsx @@ -0,0 +1,128 @@ +import React, { useState } from "react"; +import { + type ProjectItem, + getInitials, + resolveLogoSrc, + getProjectText, +} from "./projectData"; +import { getActiveSocialFields } from "./socialIcons"; + +interface ProjectCardProps { + project: ProjectItem; + onClick: (project: ProjectItem, buttonEl: HTMLButtonElement) => void; + featured?: boolean; +} + +function LogoFrame({ + logoUrl, + name, + size = "sm", +}: { + logoUrl?: string; + name: string; + size?: "sm" | "md"; +}) { + const [failed, setFailed] = useState(false); + const src = resolveLogoSrc(logoUrl); + const sizeClass = size === "sm" ? "h-14 w-14" : "h-16 w-16"; + + if (!src || failed) { + return ( + + ); + } + + return ( +
    + + ); +} + +export default function ProjectCard({ project, onClick, featured = false }: ProjectCardProps) { + const text = getProjectText(project); + const activeSocials = getActiveSocialFields(project); + const visibleSocials = activeSocials.slice(0, 3); + const displayTags = (project.tags ?? []).slice(0, 3); + const extraTagCount = (project.tags?.length ?? 0) - displayTags.length; + + return ( +
    + +
    + ); +} diff --git a/src/components/projects/ProjectDrawer.tsx b/src/components/projects/ProjectDrawer.tsx new file mode 100644 index 00000000..b2b0f056 --- /dev/null +++ b/src/components/projects/ProjectDrawer.tsx @@ -0,0 +1,337 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + type ProjectItem, + getInitials, + resolveLogoSrc, + sanitizeUrl, + sanitizeEmail, + formatMonthYear, + formatDate, +} from "./projectData"; +import { getActiveSocialFields, getSocialHref } from "./socialIcons"; + +interface ProjectDrawerProps { + project: ProjectItem | null; + open: boolean; + onClose: () => void; + triggerRef?: React.RefObject; +} + +function DrawerLogoFrame({ logoUrl, name }: { logoUrl?: string; name: string }) { + const [failed, setFailed] = useState(false); + const src = resolveLogoSrc(logoUrl); + + if (!src || failed) { + return ( + + ); + } + + return ( +
    + + ); +} + +function LeaderPhoto({ src, name }: { src: string; name: string }) { + const [failed, setFailed] = useState(false); + const safeSrc = sanitizeUrl(src); + + if (!safeSrc || failed) { + return ( +
    + {getInitials(name)} +
    + ); + } + + return ( + {name} setFailed(true)} + /> + ); +} + +export default function ProjectDrawer({ project, open, onClose, triggerRef }: ProjectDrawerProps) { + const panelRef = useRef(null); + const [visible, setVisible] = useState(false); + + // Enter: render hidden first, then transition in next frame so CSS plays on first open too. + useEffect(() => { + if (open) { + const id = requestAnimationFrame(() => setVisible(true)); + return () => cancelAnimationFrame(id); + } else { + setVisible(false); + } + }, [open]); + + // Freeze Lenis (smooth scroll) while modal is open so wheel events can't move the page. + useEffect(() => { + const lenis = (window as any).__lenis; + if (open) { + lenis?.stop(); + } else { + lenis?.start(); + } + return () => { lenis?.start(); }; + }, [open]); + + // Focus management + ESC key + useEffect(() => { + if (!open || !panelRef.current) return; + + const panel = panelRef.current; + const focusables = panel.querySelectorAll( + 'a[href], button:not([disabled]), input, textarea, select, [tabindex]:not([tabindex="-1"])' + ); + const first = focusables[0]; + const last = focusables[focusables.length - 1]; + first?.focus(); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + onClose(); + return; + } + if (e.key !== "Tab") return; + if (e.shiftKey) { + if (document.activeElement === first) { + e.preventDefault(); + last?.focus(); + } + } else { + if (document.activeElement === last) { + e.preventDefault(); + first?.focus(); + } + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [open, onClose]); + + // Restore focus to trigger on close + useEffect(() => { + if (!open && triggerRef?.current) { + triggerRef.current.focus(); + } + }, [open, triggerRef]); + + if (!project) return null; + + const activeSocials = getActiveSocialFields(project); + const websiteUrl = sanitizeUrl(project.websiteUrl); + const donationUrl = sanitizeUrl(project.donationUrl); + const involvementUrl = sanitizeUrl(project.involvementUrl); + const email = sanitizeEmail(project.publicEmail); + + return ( +
    { if (e.target === e.currentTarget) onClose(); }} + className={[ + "fixed inset-0 z-50 flex items-center justify-center px-4 py-8", + "bg-ink/60 backdrop-blur-sm", + "transition-opacity duration-300 motion-reduce:transition-none", + visible ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0", + ].join(" ")} + role="dialog" + aria-modal="true" + aria-label={`${project.name} details`} + > +
    + {/* Close button */} + + + {/* Identity block */} +
    + +
    + {project.featured && ( +

    Featured

    + )} +

    {project.name}

    + {project.categoryName && ( +

    {project.categoryName}

    + )} + {project.createdAt && ( +

    + Joined {formatMonthYear(project.createdAt)} +

    + )} +
    +
    + + {/* Primary CTAs */} +
    + {websiteUrl && ( + + Visit website + + + )} + {donationUrl && ( + + Donate + + )} + {involvementUrl && ( + + Get involved + + )} + {email && ( + + Contact + + )} +
    + +
    + + {/* About */} + {project.description && ( +
    +

    About

    +

    {project.description}

    +
    + )} + + {/* Impact callout */} + {project.impactStatement && ( +
    +

    Our impact

    +

    {project.impactStatement}

    +
    + )} + + {/* Leader */} + {project.leadName && ( +
    +

    Project lead

    +
    + {project.leaderPhoto && ( + + )} +
    +

    {project.leadName}

    + {project.leaderBio && ( +

    {project.leaderBio}

    + )} +
    +
    +
    + )} + + {/* Social links */} + {activeSocials.length > 0 && ( +
    +

    Find them online

    +
    + {activeSocials.map((field) => { + const href = getSocialHref(field, project); + if (!href) return null; + const isExternal = !field.isEmail; + return ( + + + {field.label} + + ); + })} +
    +
    + )} + + {/* Tags */} + {(project.tags?.length ?? 0) > 0 && ( +
    +

    Tags

    +
    + {project.tags!.map((tag) => ( + + {tag.name} + + ))} +
    +
    + )} + + {/* Last updated */} + {project.updatedAt && ( +

    + Last updated {formatDate(project.updatedAt)} +

    + )} +
    +
    + ); +} diff --git a/src/components/projects/ProjectsDirectory.tsx b/src/components/projects/ProjectsDirectory.tsx new file mode 100644 index 00000000..d4d3324f --- /dev/null +++ b/src/components/projects/ProjectsDirectory.tsx @@ -0,0 +1,199 @@ +import React, { useEffect, useRef, useState } from "react"; +import type { ProjectItem, Tag } from "./projectData"; +import { getProjectText } from "./projectData"; +import SearchBar from "./SearchBar"; +import TagFilter from "./TagFilter"; +import ProjectCard from "./ProjectCard"; +import ProjectDrawer from "./ProjectDrawer"; + +interface ProjectsDirectoryProps { + initialProjects?: ProjectItem[]; + initialTags?: Tag[]; +} + +function useDebounce(value: T, delay: number): T { + const [debounced, setDebounced] = useState(value); + useEffect(() => { + const timer = setTimeout(() => setDebounced(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + return debounced; +} + +function LoadingGrid() { + return ( +
    + {Array.from({ length: 6 }).map((_, i) => ( +
    + ))} +
    + ); +} + +export default function ProjectsDirectory({ + initialProjects = [], + initialTags = [], +}: ProjectsDirectoryProps) { + const [projects, setProjects] = useState(initialProjects); + const [availableTags, setAvailableTags] = useState(initialTags); + const [loading, setLoading] = useState(initialProjects.length === 0); + const [searchRaw, setSearchRaw] = useState(""); + const [activeTags, setActiveTags] = useState([]); + const [selectedProject, setSelectedProject] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); + const triggerRef = useRef(null); + + const search = useDebounce(searchRaw, 250); + + useEffect(() => { + if (initialProjects.length > 0) return; + + const fetchProjects = async () => { + setLoading(true); + try { + const res = await fetch("/api/projects", { cache: "no-cache" }); + if (res.ok) { + const data = await res.json(); + const fetched: ProjectItem[] = data.projects ?? data; + const tags: Tag[] = data.tags ?? []; + setProjects(fetched); + setAvailableTags(tags); + } + } finally { + setLoading(false); + } + }; + + fetchProjects(); + }, []); + + const isFiltering = search.trim().length > 0 || activeTags.length > 0; + + const filtered = projects.filter((p) => { + const matchesSearch = + !search.trim() || + p.name.toLowerCase().includes(search.toLowerCase()) || + getProjectText(p).toLowerCase().includes(search.toLowerCase()); + + const matchesTags = + activeTags.length === 0 || + activeTags.some((at) => (p.tags ?? []).some((t) => t.id === at.id)); + + return matchesSearch && matchesTags; + }); + + const featuredProjects = projects.filter((p) => p.featured); + const mainProjects = isFiltering ? filtered : filtered.filter((p) => !p.featured); + + const handleTagToggle = (tag: Tag) => { + setActiveTags((prev) => + prev.some((t) => t.id === tag.id) + ? prev.filter((t) => t.id !== tag.id) + : [...prev, tag] + ); + }; + + const handleCardClick = (project: ProjectItem, buttonEl: HTMLButtonElement) => { + triggerRef.current = buttonEl; + setSelectedProject(project); + setDrawerOpen(true); + }; + + const handleCloseDrawer = () => { + setDrawerOpen(false); + setTimeout(() => setSelectedProject(null), 300); + }; + + return ( + <> +
    +
    + {/* Toolbar */} +
    + + {availableTags.length > 0 && ( + setActiveTags([])} + /> + )} + {!loading && ( +

    + {isFiltering + ? `Showing ${filtered.length} of ${projects.length} projects` + : `${projects.length} projects`} +

    + )} +
    + + {loading ? ( + + ) : ( + <> + {/* Featured band — only when not actively filtering */} + {!isFiltering && featuredProjects.length > 0 && ( +
    +

    Featured

    +
    + {featuredProjects.map((project) => ( + + ))} +
    + {mainProjects.length > 0 && ( +
    + )} +
    + )} + + {/* Main grid */} + {mainProjects.length > 0 ? ( +
    + {mainProjects.map((project) => ( + + ))} +
    + ) : isFiltering ? ( +
    +

    + No projects match your filters. +

    + +
    + ) : null} + + )} +
    +
    + + + + ); +} diff --git a/src/components/projects/SearchBar.tsx b/src/components/projects/SearchBar.tsx new file mode 100644 index 00000000..e235b7a8 --- /dev/null +++ b/src/components/projects/SearchBar.tsx @@ -0,0 +1,46 @@ +import React from "react"; + +interface SearchBarProps { + value: string; + onChange: (value: string) => void; + placeholder?: string; + className?: string; +} + +export default function SearchBar({ + value, + onChange, + placeholder = "Search projects...", + className = "", +}: SearchBarProps) { + return ( + + ); +} diff --git a/src/components/projects/TagFilter.tsx b/src/components/projects/TagFilter.tsx new file mode 100644 index 00000000..6ea6417d --- /dev/null +++ b/src/components/projects/TagFilter.tsx @@ -0,0 +1,67 @@ +import React, { useState } from "react"; +import type { Tag } from "./projectData"; + +interface TagFilterProps { + tags: Tag[]; + activeTags: Tag[]; + onToggle: (tag: Tag) => void; + onClear: () => void; +} + +const MAX_VISIBLE = 10; + +export default function TagFilter({ tags, activeTags, onToggle, onClear }: TagFilterProps) { + const [showAll, setShowAll] = useState(false); + + if (tags.length === 0) return null; + + const visibleTags = showAll ? tags : tags.slice(0, MAX_VISIBLE); + const hasMore = tags.length > MAX_VISIBLE; + const activeIds = new Set(activeTags.map((t) => t.id)); + + return ( +
    +
    + {visibleTags.map((tag) => { + const isActive = activeIds.has(tag.id); + return ( + + ); + })} + + {hasMore && ( + + )} +
    + + {activeTags.length > 0 && ( + + )} +
    + ); +} diff --git a/src/components/projects/projectData.ts b/src/components/projects/projectData.ts new file mode 100644 index 00000000..044d21f6 --- /dev/null +++ b/src/components/projects/projectData.ts @@ -0,0 +1,96 @@ +export interface Tag { + id: number; + name: string; + type: string; +} + +export interface ProjectItem { + id: number; + name: string; + description: string; + impactStatement?: string; + elevatorPitch?: string; + websiteUrl?: string; + logoUrl?: string; + createdAt: string; + updatedAt: string; + leadName?: string; + leaderPhoto?: string; + leaderBio?: string; + publicEmail?: string; + donationUrl?: string; + involvementUrl?: string; + categoryName?: string; + discordUsername?: string; + mentor?: string; + twitterUrl?: string; + linkedinUrl?: string; + githubUrl?: string; + instagramUrl?: string; + facebookUrl?: string; + youtubeUrl?: string; + telegramUrl?: string; + mastodonUrl?: string; + blueskyUrl?: string; + tiktokUrl?: string; + signalUrl?: string; + upscrolledUrl?: string; + tags?: Tag[]; + featured?: boolean; +} + +/** Only allow http: and https: URLs to prevent javascript: / data: XSS vectors. */ +export function sanitizeUrl(url: string | undefined): string { + if (!url) return ""; + try { + const parsed = new URL(url, "https://placeholder.invalid"); + if (parsed.protocol === "http:" || parsed.protocol === "https:") return url; + } catch { + // malformed URL + } + return ""; +} + +export function sanitizeEmail(email: string | undefined): string { + if (!email) return ""; + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) ? email : ""; +} + +export function getInitials(name: string): string { + const words = name.trim().split(/\s+/); + if (words.length >= 2) return (words[0][0] + words[1][0]).toUpperCase(); + if (words.length === 1 && words[0].length > 0) return words[0][0].toUpperCase(); + return "P"; +} + +export function getProjectText(project: ProjectItem): string { + return project.description || project.elevatorPitch || project.impactStatement || ""; +} + +export function resolveLogoSrc(url: string | undefined): string { + if (!url) return ""; + return url.startsWith("/") ? `https://projecthub.techforpalestine.org${url}` : url; +} + +export function formatDate(dateString: string): string { + try { + return new Date(dateString).toLocaleDateString("en-US", { + month: "numeric", + day: "numeric", + year: "numeric", + }); + } catch { + return dateString; + } +} + +export function formatMonthYear(dateString: string): string { + try { + return new Date(dateString).toLocaleDateString("en-US", { + month: "long", + year: "numeric", + }); + } catch { + return ""; + } +} diff --git a/src/components/projects/socialIcons.tsx b/src/components/projects/socialIcons.tsx new file mode 100644 index 00000000..3e481668 --- /dev/null +++ b/src/components/projects/socialIcons.tsx @@ -0,0 +1,93 @@ +import React from "react"; +import GitHubIcon from "@mui/icons-material/GitHub"; +import TwitterIcon from "@mui/icons-material/Twitter"; +import LinkedInIcon from "@mui/icons-material/LinkedIn"; +import FacebookIcon from "@mui/icons-material/Facebook"; +import YouTubeIcon from "@mui/icons-material/YouTube"; +import TelegramIcon from "@mui/icons-material/Telegram"; +import InstagramIcon from "@mui/icons-material/Instagram"; +import EmailIcon from "@mui/icons-material/Email"; +import { sanitizeUrl, sanitizeEmail, type ProjectItem } from "./projectData"; + +export const GlobeIcon = () => ( + +); + +export const MastodonIcon = () => ( + +); + +export const BlueskyIcon = () => ( + +); + +export const TikTokIcon = () => ( + +); + +export const SignalIcon = () => ( + +); + +export const DiscordIcon = () => ( + +); + +export type SocialField = { + key: keyof ProjectItem; + label: string; + icon: React.ReactNode; + isEmail?: boolean; + isDiscord?: boolean; +}; + +export const SOCIAL_FIELDS: SocialField[] = [ + { key: "websiteUrl", label: "Website", icon: }, + { key: "githubUrl", label: "GitHub", icon: }, + { key: "twitterUrl", label: "Twitter / X", icon: }, + { key: "linkedinUrl", label: "LinkedIn", icon: }, + { key: "instagramUrl", label: "Instagram", icon: }, + { key: "facebookUrl", label: "Facebook", icon: }, + { key: "youtubeUrl", label: "YouTube", icon: }, + { key: "telegramUrl", label: "Telegram", icon: }, + { key: "mastodonUrl", label: "Mastodon", icon: }, + { key: "blueskyUrl", label: "Bluesky", icon: }, + { key: "tiktokUrl", label: "TikTok", icon: }, + { key: "signalUrl", label: "Signal", icon: }, + { key: "discordUsername", label: "Discord", icon: , isDiscord: true }, + { key: "publicEmail", label: "Email", icon: , isEmail: true }, +]; + +export function getSocialHref(field: SocialField, project: ProjectItem): string { + if (field.isEmail) { + const email = sanitizeEmail(project[field.key] as string | undefined); + return email ? `mailto:${email}` : ""; + } + if (field.isDiscord) { + const username = (project[field.key] as string | undefined) ?? ""; + return `https://discord.com/users/${username.replace("@", "")}`; + } + return sanitizeUrl(project[field.key] as string | undefined); +} + +export function getActiveSocialFields(project: ProjectItem): SocialField[] { + return SOCIAL_FIELDS.filter((f) => { + const val = project[f.key]; + if (!val) return false; + if (f.isEmail) return !!sanitizeEmail(val as string); + if (f.isDiscord) return true; + return !!sanitizeUrl(val as string); + }); +} diff --git a/src/components/ui/Button.astro b/src/components/ui/Button.astro new file mode 100644 index 00000000..8bb72d9e --- /dev/null +++ b/src/components/ui/Button.astro @@ -0,0 +1,43 @@ +--- +interface Props { + variant?: "primary" | "ghost" | "text"; + size?: "md" | "lg"; + href?: string; + arrow?: boolean; + class?: string; +} + +const { variant = "primary", size = "md", href, arrow = false, class: className = "" } = Astro.props; + +const base = + "ts-label inline-flex items-center gap-2 transition-all duration-150 cursor-pointer border focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand min-h-[44px]"; + +const sizes = { + md: "px-5 py-3.5", + lg: "px-10 py-5 min-[810px]:px-11", +}; + +const variants = { + primary: + "bg-brand text-white border-transparent rounded-pill hover:bg-brand-hover active:scale-[0.98]", + ghost: + "bg-transparent text-ink border-ink rounded-pill hover:bg-ink/5 active:scale-[0.98]", + text: "bg-transparent text-brand border-transparent p-0 rounded-none hover:underline", +}; + +const paddingClass = variant === "text" ? "" : sizes[size]; + +const Tag = href ? "a" : "button"; +--- + + + + {arrow && ( + + )} + diff --git a/src/layouts/HomeLayout.astro b/src/layouts/HomeLayout.astro new file mode 100644 index 00000000..0db4c190 --- /dev/null +++ b/src/layouts/HomeLayout.astro @@ -0,0 +1,182 @@ +--- +import FooterSection from "../components/home/FooterSection.astro"; +import { getEnv } from "../utils/getEnv"; +import "@fontsource/fraunces/latin-400.css"; +import "@fontsource/outfit/latin-400.css"; +import "@fontsource/outfit/latin-500.css"; +import "../styles/base.css"; +import "../styles/design-system.css"; + +interface Props { + title: string; + description?: string; + image?: string; +} + +const { + title, + description = "T4P is an incubator for Palestine advocacy projects.", + image = "https://techforpalestine.org/t4p-social-logo.png", +} = Astro.props; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const canonicalUrl = new URL( + Astro.url.pathname, + Astro.site || "https://techforpalestine.org" +).toString(); +--- + + + + + + + + + + + + + + + + {title} + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + diff --git a/src/pages/about-new.astro b/src/pages/about-new.astro new file mode 100644 index 00000000..161d7c10 --- /dev/null +++ b/src/pages/about-new.astro @@ -0,0 +1,280 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + +
    + +
    +
    +
    + +
    +

    About Tech for Palestine

    +

    + Let's make tech a driving force for Palestinian freedom and liberation +

    +

    + Technology should disrupt the status quo, not serve it. +

    +
    + + + +
    +
    + + +
    + +
    +
    + A woman working on a laptop at Gaza Sky Geeks technology center +
    +
    + Dome of the Rock in Jerusalem, Palestine, silhouetted against dramatic sunset sky with orange and purple clouds +
    +
    +
    +
    + Woman in traditional Palestinian embroidered thobe holding an olive branch, wearing ornate gold coin headpiece against olive tree background +
    +
    + Silhouette of a Palestinian father playfully tossing child in the air against Gaza cityscape at sunset with vibrant orange and purple sky +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +

    Our Founder

    +

    + Paul Biggar is a tech founder and software engineer. He founded Tech For Palestine, + CircleCI and + Darklang. +

    +

    + Read why Paul couldn't sleep — + the post that started Tech for Palestine. +

    +
    + +
    +
    +
    + + +
    +
    +
    +

    What we do

    +

    + We empower a global community of skilled tech professionals to unite in their + unwavering support of Palestine so that we can: +

    +
      +
    • + + Disrupt conventional narratives +
    • +
    • + + Capture systems of power +
    • +
    • + + Accelerate pro-Palestinian organizing and public awareness efforts, both online and off +
    • +
    +
    +
    +
    + + +
    +
    +
    +

    What we want

    +
      +
    • + + A more ethical tech industry that works to abolish the conditions for authoritarianism, surveillance, occupation, and apartheid in favor of those that promote justice, equality, and human rights. +
    • +
    • + + To draw on decades of pro-Palestinian advocacy while building the T4P community. We humbly learn from the wider community and contribute to it in aim of the main goal of liberation for Palestine. +
    • +
    • + + To be a catalyst for the reformed tech industry, aimed at abolishing apartheid and promoting human empowerment and development. +
    • +
    +
    +
    +
    + + +
    +
    +
    +

    How we work

    +
      +
    • + + We connect project leaders to communities of volunteers +
    • +
    • + + We mentor, provide office hours, and support start-up projects aimed at ending apartheid, minimizing media bias, encouraging boycotts, and more +
    • +
    • + + We create a community of pro-Palestinian techies to get involved in causes they believe in and help Palestinians get hired +
    • +
    + +
    +
    +
    + +
    +
    + Palestinian children playing joyfully in ocean waves at sunset on Gaza's Mediterranean coastline +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + diff --git a/src/pages/contact-new.astro b/src/pages/contact-new.astro new file mode 100644 index 00000000..04403490 --- /dev/null +++ b/src/pages/contact-new.astro @@ -0,0 +1,143 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const contacts = [ + { + heading: "General Questions", + description: "For general inquiries about Tech for Palestine, our projects, or how to get involved.", + linkType: "email" as const, + }, + { + heading: "Media & Press", + description: "Journalists and media professionals can find press resources and contact information on our media page.", + linkText: "Visit our Media page", + href: "/media-new", + linkType: "page" as const, + }, + { + heading: "Endorsements", + description: "Seeking a T4P endorsement for your campaign or organization? Learn what we look for and submit a request.", + linkText: "Request an endorsement", + href: "/endorsements", + linkType: "page" as const, + }, +]; +--- + + + +
    + + +
    +
    +
    +

    Tech for Palestine

    +

    + Contact Us +

    +

    + Have a question, media inquiry, or want to request an endorsement? We'd love to hear from you. +

    +
    +
    +
    + + +
    +
    + {contacts.map((item) => ( +
    +

    {item.heading}

    +

    {item.description}

    + {item.linkType === "email" ? ( + + ) : ( + + {item.linkText} → + + )} +
    + ))} +
    +
    + + +
    +
    + +
    +
    + +
    +
    + + + + + + diff --git a/src/pages/donate.astro b/src/pages/donate.astro index 7ca0c283..0fd5a792 100644 --- a/src/pages/donate.astro +++ b/src/pages/donate.astro @@ -206,72 +206,55 @@ const isUK = country === "GB";
    diff --git a/src/pages/e4p-new.astro b/src/pages/e4p-new.astro new file mode 100644 index 00000000..03c31393 --- /dev/null +++ b/src/pages/e4p-new.astro @@ -0,0 +1,491 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; +import Map from "../components/Map"; +import { Icon } from "astro-icon/components"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const benefits = [ + { + name: "Community", + description: "A supportive community of peers who share the same values", + icon: "mdi:account-group", + }, + { + name: "New Business & Networking", + description: "Opportunities to generate new business and build your professional network", + icon: "mdi:handshake", + }, + { + name: "In-Person Meetups", + description: "Invitations to in-person meetups around the world with local E4P members", + icon: "mdi:calendar-star", + }, + { + name: "Funding Introductions", + description: + "Introductions to values-aligned VCs, angel investors and other sources of funding", + icon: "mdi:cash-multiple", + }, + { + name: "Hiring Support", + description: + "Support for enriching your hiring pipeline with Palestinian and pro-Palestine talent", + icon: "mdi:account-search", + }, + { + name: "Boycott Alternatives", + description: "Lists of companies and tech products to boycott and alternatives", + icon: "mdi:swap-horizontal-circle", + }, + { + name: "PR Assistance", + description: "PR assistance by amplifying your posts and showcasing your business", + icon: "mdi:bullhorn", + }, + { + name: "Mentorship & Coaching", + description: "Mentorship and personal coaching by other E4P members", + icon: "mdi:school", + }, + { + name: "Legal Assistance", + description: "Pro bono or discounted legal assistance in case of any legal issues", + icon: "mdi:gavel", + }, +]; + +const testimonials = [ + { + name: "Chris Musei-Sequeira", + title: "Founder, CJSC — USA", + image: "/images/chris-linkedin.jpg", + linkedin: "https://www.linkedin.com/in/cjsequeira/", + quote: + "I'm so glad to be a member of Entrepreneurs for Palestine, where I know everyone stands for basic human decency. We are so many different people from so many parts of the world, but we all agree on this most fundamental value.", + }, + { + name: "Sami Abu Heiba", + title: "Founder, GIL — Palestine", + image: "/images/sami-linkedin.jpg", + linkedin: "https://www.linkedin.com/in/samiabuheiba/", + quote: + "Joining E4P has been an incredibly rich and inspiring experience. It connected me with a diverse community of purpose-driven entrepreneurs and gave me access to valuable tools, insights, and opportunities for growth. Being part of this network reminds me daily of the power of collaboration and shared impact.", + }, + { + name: "Ariana Alexander-Sefre", + title: "Founder, SPOKE — UK", + image: "/images/ariana-linkedin.jpg", + linkedin: "https://www.linkedin.com/in/arianasefre/", + quote: + "I joined E4P because I started to ask myself who I can truly trust. I don't want to work with, or take investment from, people who aren't on the right side of history. From now on, I believe it's essential to align with people and brands who will stand up to injustice when they see it. That's why E4P is a great community; because everyone here shares those values.", + }, +]; + +const founders = [ + { + name: "Aline Sara", + title: "Co-Founder, Entrepreneurs for Palestine", + image: "/images/founders/aline-sara.png", + linkedin: "https://www.linkedin.com/in/alinesara/", + }, + { + name: "Iva Gumnishka", + title: "Co-Founder", + image: "/images/founders/iva-gumnishka.png", + linkedin: "https://www.linkedin.com/in/ivagumnishka/", + }, + { + name: "Paul Biggar", + title: "Founder, Tech for Palestine", + image: "/images/founders/paul-biggar.png", + linkedin: "https://www.linkedin.com/in/paulbiggar/", + }, + { + name: "Miguel de Icaza", + title: "CEO, Xibbon", + image: "/images/founders/miguel-de-icaza.png", + linkedin: "https://www.linkedin.com/in/migueld1/", + }, + { + name: "Fadi Ghandour", + title: "Executive Chairman, Wamda Group", + image: "/images/founders/fadi-ghandour.png", + linkedin: "https://www.linkedin.com/in/fadi-ghandour-52353b/", + }, + { + name: "Amir Nathoo", + title: "Co-Founder and CEO, Outschool", + image: "/images/founders/amir-nathoo.png", + linkedin: "https://www.linkedin.com/in/amirnathoo/", + }, + { + name: "Stephanie Nadi Olson", + title: "Founder and Executive Chair of the Board, We Are Rosie", + image: "/images/founders/stephanie-nadi-olson.png", + linkedin: "https://www.linkedin.com/in/snadi/", + }, + { + name: "Reem Qawasmi", + title: "Venture Partner, Ibtikar Fund", + image: "/images/founders/reem-qawasmi.png", + linkedin: "https://www.linkedin.com/in/reemqawasmi/", + }, + { + name: "Essma Bengabsia", + title: "Founder, Bengabsia Consulting", + image: "/images/founders/essma-bengabsia.png", + linkedin: "https://www.linkedin.com/in/essmabengabsia/", + }, + { + name: "Arshan Ahmad", + title: "Co-Founder, Friday Ventures", + image: "/images/founders/arshan-ahmad.png", + linkedin: "https://www.linkedin.com/in/arshanahmad/", + }, +]; +--- + + + + +
    + +
    +
    +
    +

    Entrepreneurs for Palestine

    +

    + Entrepreneurs for Palestine +

    +

    + Entrepreneurs for Palestine is a global community of founders and CEOs who are joining + forces to stand up for what's right. As entrepreneurs, we hold a lot of collective power + which we can use in order to make a difference for the Palestinian cause. We seek to make + an impact by being intentional in how we hire, which clients and suppliers we work with, + and where we fundraise. +

    +

    + 44 countries  ·  6 continents  ·  270+ founders +

    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +

    Member benefits

    +

    What you get as a member

    + +
    + { + benefits.slice(0, 2).map((benefit, i) => ( +
    + +
    +

    {benefit.name}

    +

    + {benefit.description} +

    +
    +
    + )) + } +
    + +
    + { + benefits.slice(2).map((benefit, i) => ( +
    + +

    {benefit.name}

    +

    + {benefit.description} +

    +
    + )) + } +
    +
    +
    +
    + + +
    +
    +
    +

    Our reach

    +

    A global community

    +
    + +
    +
    +
    +
    + + +
    +
    +
    +

    The E4P Pledge

    +
    + “Be intentional in how you hire, which clients and suppliers you work with, and + where you fundraise.” +
    + + Sign the E4P Pledge + + +
    +
    +
    + + +
    +
    +
    +

    From our members

    +

    What members are saying

    +
    + { + testimonials.map((t) => ( +
    + +

    {t.quote}

    + +
    + )) + } +
    +
    +
    +
    + + +
    +
    +
    +

    Who's behind E4P

    +

    Meet the founders

    +
    + { + founders.map((f) => ( + + {f.name} +

    {f.name}

    +

    {f.title}

    + +
    + )) + } +
    +
    +
    +
    + + +
    +
    +
    +

    Get involved

    +

    Ready to join?

    +

    + If you're an entrepreneur, schedule a short introductory call based on your region. +

    +
    +
    +

    Americas

    +
    + +
    +
    +
    +

    Europe & Asia

    +
    + +
    +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + diff --git a/src/pages/faq-new.astro b/src/pages/faq-new.astro new file mode 100644 index 00000000..78ec4c5a --- /dev/null +++ b/src/pages/faq-new.astro @@ -0,0 +1,93 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import FAQSection from "../components/FAQSection.tsx"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + +
    + + +
    +
    +
    +

    Tech for Palestine

    +

    + Frequently asked questions +

    +

    + Everything you need to know about our mission, projects, and how to get involved. +

    +
    +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    + + + + diff --git a/src/pages/get-involved-new.astro b/src/pages/get-involved-new.astro new file mode 100644 index 00000000..10cf7d78 --- /dev/null +++ b/src/pages/get-involved-new.astro @@ -0,0 +1,409 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + + +
    + +
    +
    +
    +

    Get Involved

    +

    + There's a place for you in this work. +

    +

    + Join a community of technologists, builders, and advocates working for Palestinian liberation. However you want to contribute, there's a way in. +

    +
    + + +
    +
    +
    +
    +
    +
    +
    + + +
    + +
    + + +
    +
    +
    +

    Stay connected

    +

    Get project updates

    +

    + Stay informed about new project opportunities and T4P initiatives. +

    +
    +
    + +
    +
    +
    + + +
    +
    +
    +

    + Not sure which path is right for you? We'd love to hear from you. +

    + +
    +
    +
    +
    +
    + + + + diff --git a/src/pages/help/hire-new.astro b/src/pages/help/hire-new.astro new file mode 100644 index 00000000..59627600 --- /dev/null +++ b/src/pages/help/hire-new.astro @@ -0,0 +1,289 @@ +--- +import { getEnv } from "../../utils/getEnv"; +import HomeLayout from "../../layouts/HomeLayout.astro"; +import HomeNavbar from "../../components/home/HomeNavbar.astro"; +import SignUpForm from "../../structures/SignUpForm.astro"; +import { Icon } from "astro-icon/components"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const contacts = [ + { + name: "Instagram", + url: "https://www.instagram.com/techforpalestine", + icon: "mdi:instagram", + }, + { + name: "Twitter", + url: "https://twitter.com/tech4palestine", + icon: "mdi:twitter", + }, + { + name: "GitHub", + url: "https://github.com/TechForPalestine/", + icon: "mdi:github", + }, + { + name: "Discord", + url: "https://techforpalestine.org/discord-invite", + icon: "ic:baseline-discord", + }, +]; + +function generateLabel(name: string): string { + const map: Record = { + Upwork: "Hire on Upwork", + Dribble: "View on Dribble", + TAP: "View TAP Program", + "Manara.tech": "Hire via Manara", + "Rising from Ashes": "Hire via Rising Bees", + Youmna: "Meet Your Assistant", + }; + const key = Object.keys(map).find((k) => name.includes(k)); + return key ? map[key] : `Explore ${name}`; +} + +const resourceSections = [ + { + name: "Recruitment Platforms", + overline: "Hire from Palestine", + bg: "bg-sand" as const, + cardBg: "bg-page" as const, + resources: [ + { + name: "Apricot", + url: "https://apricotinternational.org/", + logo: "/apricot.jpg", + description: `Apricot connects engineering talent in Palestine with international teams.`, + }, + { + name: "MENA Alliances", + url: "https://menaalliances.com/jobsforpalestine/", + logo: "/mena-alliances-logo.webp", + description: `MENA Alliance sources tech talent from Palestine for global companies.`, + }, + { + name: "Gaza Sky Geeks", + url: "https://gazaskygeeks.com/", + logo: "/gaza-sky-geeks-logo.png", + description: `A leading tech hub in Gaza and the West Bank, fostering future tech leaders since 2011.`, + }, + { + name: "Manara.tech", + url: "https://manara.tech/tech-jobs-for-palestine", + logo: "/manara.png", + description: `Connecting top tech talent in the MENA region to global jobs.`, + }, + { + name: "Rising from Ashes", + url: "https://bees.to/hire-ps/", + logo: "/bees-logo-horizontal.webp", + description: `Rising Bees connects Palestinians with remote IT jobs.`, + }, + { + name: "TAP", + url: "https://www.tapcareers.io/", + logo: "/tap-logo.svg", + description: `TAP offers career acceleration in Palestine and Jordan with remote-ready talent.`, + }, + { + name: "Techtative", + url: "https://techtative.io/", + logo: "/techtative-logo.png", + description: `Premier EOR in Palestine, helping global orgs tap into local expertise.`, + }, + { + name: "Olives and Heather", + url: "https://www.olivesandheather.com/", + logo: "/olives-and-heather.png", + description: `Empowering Palestinian marketers through global marketing excellence.`, + }, + { + name: "Gaza Talents", + url: "https://gazatalents.com/en", + logo: "/gazatalents.png", + description: `Quick access to skilled professionals from Gaza.`, + }, + { + name: "Youmna", + url: "https://www.withyoumna.com/", + logo: "/youmna.png", + description: `Remote Executive Assistants from Palestine — your "right hand".`, + }, + { + name: "BrightGaza", + url: "https://brightgaza.com/", + logo: "/brightgaza-logo.svg", + description: `BrightGaza connects talented freelancers in Gaza with real remote work opportunities around the world, helping them grow professionally, earn income, and build sustainable careers.`, + }, + ], + }, + { + name: "Freelance Networks", + overline: "Freelancers", + bg: "bg-page" as const, + cardBg: "bg-sand" as const, + resources: [ + { + name: "Dribble", + url: "https://dribbble.com/freelance-ux-designers-for-hire-gaza-ps", + logo: "/dribbble-logo.png", + description: `UX designers from Gaza offering freelance services.`, + }, + { + name: "Upwork", + url: "https://www.upwork.com/hire/ps/", + logo: "/upwork.png", + description: `Find Palestinian freelancers for your projects on Upwork.`, + }, + ], + }, +]; +--- + + + + +
    + +
    +
    +
    +

    Jobs for Palestine

    +

    + Hire top talent from Palestine and the Middle East +

    +

    + Get help hiring engineers, designers, and technologists from across the region. +

    +

    + Know another good resource? Let us know: + { + contacts.map((contact) => ( + + {contact.name} + + + )) + } +

    +
    +
    +
    + + + { + resourceSections.map((section) => ( +
    +
    +
    +

    {section.overline}

    +

    {section.name}

    + +
    +
    +
    + )) + } + + +
    +
    + +
    +
    +
    +
    + + + + diff --git a/src/pages/home-new.astro b/src/pages/home-new.astro new file mode 100644 index 00000000..6384489d --- /dev/null +++ b/src/pages/home-new.astro @@ -0,0 +1,54 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HeroSection from "../components/home/HeroSection.astro"; +import ManifestoSection from "../components/home/ManifestoSection.astro"; +import WhySection from "../components/home/WhySection.astro"; +import PortfolioSection from "../components/home/PortfolioSection.astro"; +import SupportSection from "../components/home/SupportSection.astro"; +import TestimonialsSection from "../components/home/TestimonialsSection.astro"; +import OpeningsSection from "../components/home/OpeningsSection.astro"; +import PressSection from "../components/home/PressSection.astro"; +import CtaSection from "../components/home/CtaSection.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +let openRoles: Array<{ + id: string; + title: string; + description: string | null; + skillCategories: string[]; + timeCommitment: string | null; + type: "team" | "project"; + team: { name: string } | null; + project: { id: string; name: string } | null; +}> = []; + +try { + const res = await fetch("https://hub.techforpalestine.org/api/public/open-roles"); + if (res.ok) { + openRoles = await res.json(); + } +} catch {} +--- + + + + + +
    + + + + + + + {openRoles.length > 0 && } + + +
    +
    diff --git a/src/pages/ideas-new.astro b/src/pages/ideas-new.astro new file mode 100644 index 00000000..63a82109 --- /dev/null +++ b/src/pages/ideas-new.astro @@ -0,0 +1,179 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import IdeasWithTabsNew from "../components/IdeasWithTabsNew"; +import { fetchNotionIdeas } from "../store/notionClient.js"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +let rawIdeas: any[] = []; +try { + rawIdeas = await fetchNotionIdeas(Astro.locals); +} catch (error) { + console.error("Failed to fetch ideas from Notion:", error); +} + +const mapIdea = (idea: any) => { + const plainText = idea.description.map((s: any) => s.plain_text).join(""); + return { + id: idea.id, + data: { title: idea.name, tags: [] }, + richTextDescription: idea.description, + excerpt: plainText.substring(0, 150) + (plainText.length > 150 ? "..." : ""), + }; +}; + +const newIdeas = rawIdeas.filter((i) => i.category === "new").map(mapIdea); +const existingIdeas = rawIdeas.filter((i) => i.category === "existing").map(mapIdea); +const startedIdeas = rawIdeas.filter((i) => i.category === "started").map(mapIdea); +--- + + + + +
    + +
    +
    +
    +

    Incubator ideas

    +

    + Turn bold ideas into real impact +

    +

    + Tech for Palestine builds projects that support Palestinian freedom and counter + pro-Israel propaganda. We need leaders to make them real — explore what's waiting for + someone like you. +

    +
    + + +
    +
    + +
    +
    +
    + + +
    + +
    + + +
    +
    +
    +
    +

    Generating new ideas

    +

    + There are endless pro-Israel initiatives that need countering. Consider targeting: +

    +
      +
    • + + A politician's complicity or ties to Israel +
    • +
    • + + Someone who committed violence against protestors +
    • +
    • + + A pro-Israel hasbara organization +
    • +
    • + + An entity's investments in Israel +
    • +
    • + + A US non-profit funding the genocide (e.g. Friends of the IDF) +
    • +
    +
    + +
    +

    How to apply

    +

    + Want to lead an existing project or bring a new idea to life? Submit through the + Project Hub and our team will follow up. +

    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/src/pages/incubator-new.astro b/src/pages/incubator-new.astro index ea21bf22..bfe6aa76 100644 --- a/src/pages/incubator-new.astro +++ b/src/pages/incubator-new.astro @@ -1,117 +1,271 @@ --- -import Layout from "../layouts/Layout.astro"; -import "../styles/base.css"; +import { getEnv } from "../utils/getEnv"; +import { Icon } from "astro-icon/components"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; --- - -
    - -
    -
    -

    The Tech for Palestine Incubator

    -

    - Tech for Palestine's mission is to create a vast ecosystem of projects for Palestinian - liberation that push back against racism and occupation and address every part of the - hasbara ecosystem, as well as promoting, advocating, and sharing the unique aspects of - Palestine's struggle for liberation. -

    + + +
    + + +
    +
    +
    +

    T4P Incubator

    +

    + Turn your idea for Palestine into something that scales +

    +

    + The Tech for Palestine Incubator supports early-stage advocacy projects with mentorship, volunteers, funding, and a network that's launched 60+ initiatives. +

    +
    + + +
    +
    - -
    -
    -
    -

    Our Theory of Change

    -

    - Our theory of change is that thousands of high-impact, pro-Palestinian advocacy projects - are needed to counter the massive flood of anti-Palestinian propaganda, especially in - the west. We started a project incubator because we saw many highly motivated, dedicated - people with great ideas struggling to bring their ideas to life and produce the outcomes - they were after. Our main purpose is to provide support. + +

    +
    +
    +

    Our theory of change

    +

    + Thousands of high-impact, pro-Palestinian advocacy projects are needed to counter the massive flood of anti-Palestinian propaganda, especially in the west. We started a project incubator because we saw highly motivated people with great ideas struggling to bring them to life. Our purpose is to provide the support, expertise, and connections that turn good ideas into projects with real reach.

    -
    - - Incubator Vision +
    +
    + + +
    +
    +
    +

    What the incubator provides

    +
      +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    - -
    -
    -

    How It Works

    -

    - We accept a limited number of projects to support with our expertise, network, and - funding, but are happy to promote other community and affiliated projects that could use - some airtime. -

    -
    - { - [ - "Fill out and submit the incubator application", - "If we understand and appreciate your project, we will request an interview", - "If we don't understand or think it isn't a fit, we may ask for more info or send a rejection email (no harm/foul)", - "If accepted: onboarding email, book a strategy call, attend an onboarding meeting, and gain access to Discord channels and Notion", - ].map((step) => ( -
    -
    - - - - - -
    -
    -

    {step}

    -
    -
    - )) - } + +
    +
    +
    +

    Who the incubator is for

    +

    + Any early-stage project that directly or indirectly advocates for Palestine, where we believe you can have impact and scale. Our projects often work on scaling boycotts, protest tech, tech industry divestment, media bias, reaching new audiences, Silicon Valley partnerships, AI bias, and exposing hasbara. +

    +

    + Whether you're just getting started or have an experienced team looking for connections and strategic advice, the incubator is built to meet you where you are. +

    - -
    -
    -
    -

    What You Get

    -
      -
    • Join the Discord community
    • -
    • Office Hours or 1v1 Mentorship
    • -
    • Volunteering/Marketing help
    • -
    • Connection to our network
    • + +
      +
      + Palestinian children smiling together +
      +
      + + +
      +
      +
      +

      What we expect

      +
        +
      • + + Focus on impact, such as reaching 10 million users +
      • +
      • + + Commitment to keep working on your project and driving it forward +
      • +
      • + + Communication through office hours (weekly for the first 6 weeks, monthly after) and regular updates +
      • +
      • + + Flexible participation: if the incubator no longer fits your needs, membership may be removed without hard feelings +
      +

      + We don't require T4P branding, ownership of your work, daily meetings, or use of specific tools. Use whatever works best for your project. +

      -
      -

      What We Expect

      -
        -
      • Dedication to impact
      • -
      • Office hours attendance
      • -
      • Community engagement
      • -
      • Transparency
      • -
      +
      +
      + + +
      +
      +
      +

      How to apply

      +
        +
      1. + 1 + Application form: fill out the application (takes 10 to 30 minutes) +
      2. +
      3. + 2 + Review: we review your application and invite selected projects for an interview. We may reach out for more information. +
      4. +
      5. + 3 + Interview: a 30-minute call focusing on the impact you intend to have and how you plan to get there. No slides needed. +
      6. +
      7. + 4 + Decision: we aim to notify all applicants within 10 days of application. Interviews and decisions happen twice weekly. +
      8. +
      9. + 5 + Onboarding: if admitted, you receive next steps and access to all Tech for Palestine resources. +
      10. +
      - -
      -
      -

      We Don't Require

      -
        -
      • t4p branding
      • -
      • a cut of your profit
      • -
      • use of our resources for organization
      • -
      + +
      +
      +
      +

      Ready to get started?

      +
      +
      +

      Have an idea and ready to build?

      + +
      +
      +

      Want to lead a project but need inspiration?

      + +
      +
      +

      Want to contribute your skills to existing projects?

      + +
      +
      +

      + If you have already applied, we'll reach out to you soon. +

      +
      + + +
      +
      + +
      +
      +
    - +
    + + + + diff --git a/src/pages/index-new.astro b/src/pages/index-new.astro index e5d2128d..da86b35e 100644 --- a/src/pages/index-new.astro +++ b/src/pages/index-new.astro @@ -55,7 +55,7 @@ import "../styles/base.css";
    Meet our team diff --git a/src/pages/legal-new.astro b/src/pages/legal-new.astro new file mode 100644 index 00000000..4e053094 --- /dev/null +++ b/src/pages/legal-new.astro @@ -0,0 +1,166 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + +
    + + +
    +
    +
    +

    Legal · Tech for Palestine

    +

    + Financial & Legal Notice +

    +

    + Our legal structure, financial transparency, and compliance information. +

    +
    +
    +
    + +
    +

    501(c)(3) Tax-Exempt Organization

    +

    + Tech for Palestine is a registered 501(c)(3) nonprofit with the Internal Revenue Service. +

    +
      +
    • + + EIN: 99-3441367 +
    • +
    • + + Registered Address: 548 Market St #266950, San Francisco, California 94104-5401 US +
    • +
    • + + IRS Determination Letter: Available upon request +
    • +
    +
    + +
    +

    Mission Statement

    +

    + Our mission is to harness technology and human innovation for a free and truly sovereign Palestine. All charitable contributions are used at the discretion of Tech for Palestine for furthering this mission and our programs according to our governing documents and applicable law. +

    +
    + +
    +

    Tax Deductibility

    +

    + Contributions to Tech for Palestine may be deductible as charitable donations for federal tax purposes to the extent allowed by law. Please consult your tax advisor for details specific to your situation, as we do not provide tax advice. +

    +
    + +
    +

    Financial Transparency

    +

    + We strive for transparency in our operations and finances. Our IRS Form 990s and financial information are available at our Candid/Guidestar profile or by request when legally permissible. +

    +
    + +
    +

    Legal Disclaimers

    +
      +
    • + + No Legal or Tax Advice: Information on this website is provided for informational purposes only and does not constitute legal or tax advice. +
    • +
    • + + No Guarantee: While we strive for accuracy, all information is provided without warranty of any kind and subject to change. +
    • +
    +
    + +
    +

    Anti-Discrimination Policy

    +

    + We do not discriminate based on race, color, national origin, religion, sex, gender identity, sexual orientation, age, or disability in our programs, activities, or employment practices. +

    +
    + +
    +

    Governance

    +

    + Copies of our current whistleblower and conflict of interest policies are available upon request to ensure transparency and accountability in our operations. +

    +
    + +
    +

    Reporting Concerns

    +

    + Suspected fraud, misuse of funds, or other concerns may be reported to contact@techforpalestine.org or to your state's Attorney General as appropriate. +

    +
    + +
    +

    Contact Information

    +
    +

    Tech for Palestine

    +

    548 Market St #266950

    +

    San Francisco, California 94104-5401

    +

    United States

    +

    + Email:{" "} + contact@techforpalestine.org +

    +
    +

    + For questions about our privacy practices, please see our Privacy Policy. For terms governing your use of our services, please see our Terms of Service. +

    +
    + +
    +
    +
    + +
    + + + + + diff --git a/src/pages/london-gathering-new.astro b/src/pages/london-gathering-new.astro new file mode 100644 index 00000000..0ea4b64e --- /dev/null +++ b/src/pages/london-gathering-new.astro @@ -0,0 +1,396 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; +import { Icon } from "astro-icon/components"; +import { fetchNotionAgenda } from "../store/notionClient"; +import SpeakersSection from "../components/london-gathering/SpeakersSection"; +import AgendaSection from "../components/london-gathering/AgendaSection"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +let agendaData: any = { agendaItems: [], speakers: [] }; +try { + agendaData = await fetchNotionAgenda(Astro.locals); +} catch (error) { + console.error("Error fetching agenda data:", error); +} + +const themes = [ + { name: "Scaling Up Boycotts", icon: "mdi:cart-off" }, + { name: "Media Bias and Hasbara", icon: "mdi:bullhorn" }, + { name: "Protests and Activism", icon: "mdi:account-group" }, + { name: "Palestinian Tech Sector", icon: "mdi:domain" }, + { name: "MedTech for Palestine", icon: "mdi:medical-bag" }, + { name: "Hiring Palestinians", icon: "mdi:briefcase-search" }, + { name: "Social Media Censorship", icon: "mdi:cancel" }, + { name: "Investors for Palestine", icon: "mdi:trending-up" }, +]; + +const stats = [ + { number: "70+", label: "Incubator", description: "Impactful advocacy projects", href: "/projects-new" }, + { number: "9K+", label: "Community", description: "Tech workers worldwide", href: "/about-new" }, + { number: "1K+", label: "Volunteers", description: "Donate skills to projects you care about", href: "/volunteer-new" }, + { number: "250+", label: "Entrepreneurs", description: "Pro-Palestine entrepreneurs", href: "/e4p-new" }, +]; +--- + + + + +
    + +
    +
    +
    +

    London, November 8 2025

    +

    + Tech for Palestine Gathering +

    +
    + + + Palestine House + + + + 9:00am - 4:00pm + +
    +

    + A full day event for T4P community members with workshops and networking + opportunities. +

    + Sold Out +
    +
    +
    + + +
    +
    +
    +

    What we covered

    +

    Themes

    +
    + { + themes.map((theme) => ( + + + {theme.name} + + )) + } +
    +
    +
    +
    + + +
    +
    +
    +

    About

    +

    About the event

    +
    +
    +

    + The Tech for Palestine Community Gathering brought together 100 professionals who + work at the intersection of tech and pro-Palestine activism. +

    +

    + Whether you work in the field of combatting media bias, scaling boycotts, making + protests safer, or building alternatives to big tech platforms, this event was for + you. +

    +

    + The gathering was also open to entrepreneurs, investors and organizers who wanted + to network with innovators and support their work. +

    + Sold Out +
    +
    + Tech for Palestine community gathering +
    +
    +
    +
    +
    + + +
    +
    +
    +

    Venue

    +

    Location

    +
    +
    + Palestine House location in London +
    +
    +

    London

    +
    +
    +
    + +
    +
    +

    9:00am - 4:00pm

    +

    Full day event

    +
    +
    +
    +
    + +
    +
    +

    Palestine House

    +

    + 113 High Holborn
    London WC1V 6JQ +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + +
    +
    +
    +

    The movement

    +

    About Tech for Palestine

    +
    +

    + We bring together a global community of tech professionals who support the + Palestinian liberation movement and want to contribute their expertise to + meaningful, impactful tech projects. +

    +
    + { + stats.map((stat) => ( + +

    {stat.number}

    +

    {stat.label}

    +

    {stat.description}

    +
    + )) + } +
    +
    +
    +
    +
    + + +
    +
    +
    +

    Thank you

    +

    Sponsors

    + + + + +
    +

    Interested in becoming a sponsor?

    +

    + Contact us to receive our sponsorship package and learn more about T4P initiatives. +

    + +
    +
    +
    +
    + + +
    +
    +
    +

    Ready to join us?

    +

    + Don't miss this opportunity to connect with fellow tech professionals supporting + Palestine and learn about impactful projects. +

    + Sold Out +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + diff --git a/src/pages/media-new.astro b/src/pages/media-new.astro new file mode 100644 index 00000000..917b6f4f --- /dev/null +++ b/src/pages/media-new.astro @@ -0,0 +1,645 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const logos = [ + { src: "https://upload.wikimedia.org/wikipedia/commons/7/75/The_Guardian_2018.svg", alt: "The Guardian" }, + { src: "https://e7.pngegg.com/pngimages/669/74/png-clipart-tc-techcrunch-logo-illustration-techcrunch-logo-icons-logos-emojis-iconic-brands.png", alt: "TechCrunch" }, + { src: "https://upload.wikimedia.org/wikipedia/en/thumb/8/8f/Al_Jazeera_Media_Network_Logo.svg/1280px-Al_Jazeera_Media_Network_Logo.svg.png", alt: "Al Jazeera" }, + { src: "https://upload.wikimedia.org/wikipedia/commons/d/d6/Inc._%28business_magazine%29_logo.svg", alt: "Inc." }, + { src: "https://ssir.org/images/resources/SSIR_Logo.RGB_Black_984x234.png", alt: "Stanford Social Innovation Review" }, + { src: "https://upload.wikimedia.org/wikipedia/commons/4/4f/TRT_World_logosu.png", alt: "TRT World" }, +]; + +const leadArticle = { + publication: "Stanford Social Innovation Review", + date: "2024", + headline: "Tech for Palestine Business Incubator Supports Entrepreneurs Protecting Human Rights", + standfirst: "A Stanford Social Innovation Review deep-dive into how Tech for Palestine's incubator model is backing founders building tools for human rights and Palestinian liberation.", + url: "https://ssir.org/articles/entry/tech-against-genocide", +}; + +const articles = [ + { + publication: "TechCrunch", + date: "Jan 2024", + headline: "Tech for Palestine launches to provide tools to help support Palestinians", + url: "https://techcrunch.com/2024/01/02/tech-for-palestine-launches-to-provide-tools-and-projects-to-help-advocate-for-palestinians/", + }, + { + publication: "Al Jazeera", + date: "Jan 2026", + headline: "What's UpScrolled, the app gaining popularity after TikTok's US takeover?", + url: "https://www.aljazeera.com/news/2026/1/29/whats-upscrolled-the-app-gaining-popularity-after-tiktoks-us-takeover", + }, + { + publication: "The Guardian", + date: "Nov 2025", + headline: "Fundraisers warn of 'catastrophic' drop in donations to Gaza since ceasefire", + url: "https://www.theguardian.com/global-development/2025/nov/21/fundraisers-warn-catastrophic-drop-donations-aid-palestinians-gaza-israel-ceasefire", + }, + { + publication: "TRT World", + date: "2024", + headline: "Tech for Palestine initiative supports advocacy and accountability amid Israel's use of AI", + url: "https://www.trtworld.com/video/18268907", + }, + { + publication: "Palestine Chronicle", + date: "Jan 2024", + headline: "'Tech for Palestine' Aims to Boost Global Palestinian Advocacy", + url: "https://www.palestinechronicle.com/tech-for-palestine-aims-to-boost-global-palestinian-advocacy/", + }, + { + publication: "The Daily Star", + date: "Jan 2024", + headline: "'Tech for Palestine' Initiative Launched to Support Palestinians", + url: "https://www.thedailystar.net/tech-startup/news/tech-palestine-initiative-launched-support-palestinians-3510791", + }, + { + publication: "Muslim Tech Wire", + date: "Jan 2024", + headline: "Tech for Palestine — A Global Call", + url: "https://muslimtechwire.com/tech-for-palestine-launches", + }, + { + publication: "Counter Points", + date: "2024", + headline: "Paul on Counter Points, Tech for Palestine", + url: "https://www.youtube.com/watch?v=UldcFPEZBM4&t=4388s", + }, +]; + +const interviews = [ + { + id: "SZYxUoHYPIg", + title: "Gaza Diaries: Paul Biggar & Dr. Omar Suleiman", + description: "Founder Paul Biggar joins Dr. Omar Suleiman to discuss the role of tech in the Palestinian liberation movement.", + }, + { + id: "LzXASFo-cf0", + title: "How Big Tech Enables Israel's Crimes", + description: "A deep conversation on the relationship between the tech industry and Israeli military operations.", + }, + { + id: "lQ5DLKLqUtg", + title: "Voices of Impact: Tech for Palestine's Journey", + description: "An inside look at how Tech for Palestine grew from a single blog post to a global coalition of advocates.", + }, + { + id: "8mdYIozAgBw", + title: "TRT World Interview", + description: "TRT World sits down with Tech for Palestine on tech accountability and advocacy amid the ongoing genocide.", + }, +]; + +const lightAssets = [ + { label: "Logo (PNG)", file: "/logos/Logo.png" }, + { label: "Logo (SVG)", file: "/logos/Logo.svg" }, + { label: "Logo Transparent", file: "/logos/Logo---Transparent.png" }, + { label: "Mark (PNG)", file: "/logos/Mark.png" }, + { label: "Mark Transparent", file: "/logos/Mark---Transparent.png" }, + { label: "Brand Colors", file: "/logos/Colors.png" }, +]; + +const darkAssets = [ + { label: "Logo Dark", file: "/logos/Logo-dark.png" }, + { label: "Mark Dark", file: "/logos/Mark-dark.png" }, +]; +--- + + + + +
    + + +
    +
    + + +
    +

    Media & Press

    +

    + Interviews, news coverage, and thought leadership about Tech for Palestine. +

    +
    +
    + + +
    +

    Media inquiries

    + + media@techforpalestine.org + +

    For press inquiries, interviews, or media requests.

    +
    + + +
    +

    For media

    +

    Press kit

    +

    + Tech for Palestine is a 501(c)(3) nonprofit that incubates and scales tech and advocacy projects for Palestinian liberation. Our community spans thousands of volunteers, engineers, designers, and advocates worldwide. Use the assets below in coverage, attribution, and partner materials. +

    +
    + + +
    +
    + + +
    +

    Official logos

    + + + + + +
    +

    Dark variants · for use on dark backgrounds

    + +
    +
    + {[ + { href: "/logos/Logo.ai", label: "Logo (Adobe Illustrator)" }, + { href: "/logos/Logo.pdf", label: "Logo (PDF)" }, + { href: "/logos/Font-20240919T051150Z-001.zip", label: "Fonts (ZIP)" }, + ].map(({ href, label }) => ( + + + {label} + + ))} +
    +
    + +
    +
    + + +
    + +
    + + +
    +
    + +
    +

    On camera

    +

    Interviews

    +
    + +
    + {interviews.map((interview) => ( +
    +
    + +
    +
    +

    {interview.title}

    +

    {interview.description}

    +
    +
    + ))} +
    + +
    +
    + + +
    +
    +
    +

    Stay informed

    +

    Newsletter archive

    +

    + Browse past updates from Tech for Palestine to stay informed on recent campaigns and progress. +

    + +
    +
    +
    + +
    +
    + + + + diff --git a/src/pages/membership-new.astro b/src/pages/membership-new.astro new file mode 100644 index 00000000..256aefe3 --- /dev/null +++ b/src/pages/membership-new.astro @@ -0,0 +1,271 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; +import MembershipDues from "../components/membership/MembershipDues"; +import MembershipFAQSection from "../components/membership/MembershipFAQSection"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const teams = [ + { + name: "Coaching for Palestinian Entrepreneurs", + description: + "Support Palestinian entrepreneurs with job skills for professional growth.", + }, + { + name: "Tech Accountability", + description: + "Develop content like blogs, social media posts, and more to hold tech companies accountable for supporting Israel's war crimes.", + }, + { + name: "Legal Aid", + description: + "Help connect pro-bono resources from law firms to cases of discrimination against the Palestinian identity or pro-Palestinian speech.", + }, + { + name: "US Events", + description: + "Organize in-person T4P events in the US to help grow the movement.", + }, + { + name: "T4P Hackathons", + description: + "Arrange hackathons to kickstart software solutions to movement problems.", + }, + { + name: "Boycott Search", + description: + "Develop a search engine that aggregates boycott targets and alternatives to Zionist resources, providing a central entry point to easily navigate boycotting information across organizations.", + }, +]; + +--- + + + + +
    + +
    +
    +
    +

    Membership

    +

    + The most direct way to support{" "} + Tech for Palestine +

    +

    + Members fund and shape T4P's work directly: through dues that sustain our + initiatives, and through teams that scale the movement for Palestinian + liberation. +

    +
    + + +
    +
    +
    +
    + + +
    +
    + Tech for Palestine community members applauding at an event +
    +
    + + +
    +
    +
    +

    Why join

    +

    + Becoming a member is the best way to support Tech for Palestine +

    +

    + Members support T4P's work directly through dues and by joining teams to scale + the movement, advocate for non-complicit tech and collaborate for Palestinian + liberation. +

    +

    + Membership dues support all T4P initiatives, including the Incubator, the 90+ + external projects we support, and teams and campaigns working on tech + complicity. Our best known successes include UpScrolled, Boycat, Find a + Protest, Apricot, and Thaura AI. +

    +

    + After joining, you'll be invited to join Hub, + our member portal, where you can: +

    +
      +
    • + + Join our advocacy and support teams, working directly on Palestinian + liberation +
    • +
    • + + Start a team of your own, with T4P support and resources to help you grow +
    • +
    +

    + Whether you're a thinker, builder, leader, software developer, marketer, or + activist, there's a place for you to contribute in your own way. +

    +
    +
    +
    + + +
    +
    +
    +

    Working committees

    +

    A few examples of our teams

    +
    + { + teams.map((team) => ( +
    +

    {team.name}

    +

    + {team.description} +

    +
    + )) + } +
    +
    +
    +
    + + +
    +
    +
    +

    Membership dues

    +

    Join today

    + +
    +
    +
    + + +
    +
    +
    +

    Questions

    +

    Membership FAQ

    + +
    + +
    + + +

    + For additional questions, email{" "} + + membership@techforpalestine.org + + . +

    + + +
    + Tech for Palestine community event +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + diff --git a/src/pages/mentorship-new.astro b/src/pages/mentorship-new.astro new file mode 100644 index 00000000..4953c597 --- /dev/null +++ b/src/pages/mentorship-new.astro @@ -0,0 +1,315 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + + +
    + +
    +
    +
    +

    Mentorship Program

    +

    + Become a T4P Mentor +

    +

    + Support T4P Incubator projects with expertise and guidance. Help Palestinian tech + projects find product-market fit and grow to make a real impact. +

    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +

    About the program

    +

    The T4P Mentorship Program

    +

    + Tech for Palestine is currently looking for mentors who will engage with project leaders + from the{" "} + Tech for Palestine Incubator{" "} + through weekly 1:1 mentorship sessions. +

    +

    + The goal is to support the projects in finding product market fit and growing to make + considerable impact, such as reaching tens of millions of users. +

    +

    + With over 50 projects in the Incubator and counting, we aim to build a whole ecosystem of + pro-Palestine tech which works towards a Free Palestine. +

    +
    +
    +
    + + +
    +
    +
    +

    Who we're looking for

    +

    Ideal profile

    +
      +
    • + + Have you contributed to bringing a product to life and ensured its market fit, in the areas + of technology, entrepreneurship, venture building, or nonprofit innovation? +
    • +
    • + + Do you have a strong alignment with T4P values and purpose? +
    • +
    • + + Do you have previous mentoring, coaching, or teaching experience? +
    • +
    • + + Do you possess excellent communication skills, and the ability to give actionable feedback + with empathy? +
    • +
    • + + Are you available to commit 5 hours+ per month per project for an extended period of time? +
    • +
    +
    +
    +
    + + +
    +
    +
    +

    Getting started

    +

    How it works

    +

    + Once you fill out the Mentor Application, your profile will be screened and if it + corresponds to our ideal profile, you will be invited to an interview with the Tech for + Palestine team. +

    +

    + After the interview, you will become part of a database of potential mentors which project + leaders will have access to. Each project leader will be able to request a mentor, in + which case you will be introduced to begin your mentorship journey. +

    +
    +
    +
    + + +
    +
    +
    +

    What you'll do

    +

    Key responsibilities

    +
      +
    • + + Provide 1:1 or small group mentorship to assigned projects(s) on a weekly basis. +
    • +
    • + + Keep project leaders accountable for attending mentorship sessions and making progress week + over week. +
    • +
    • + + Share expertise in areas such as: Product strategy, product development, business models, + product engineering blueprints, Go-to-market, marketing, and impact measurement. +
    • +
    • + + Guide founders in fine-tuning their product strategy and business models, organize their + priorities, shape their MVPs, and iteratively develop their products. +
    • +
    • + + Focus on asking questions, discussing assumptions, and pointing out inconsistencies, so that + projects find the answers themselves, rather than telling them what to do. +
    • +
    • + + Participate in demo days, mentor roundtables, or feedback sessions when possible. +
    • +
    • + + Serve as a trusted advisor and role model who champions advocacy for Palestine, perseverance + and commitment, creating impactful products, and taking hard decisions. +
    • +
    +
    +
    +
    + + +
    +
    +
    +

    Our approach

    +

    T4P Principles

    +
    +
    +

    We do

    +
      +
    • + + Invest time and resources only in tech projects with a high potential possible impact + on Palestine/Palestinians +
    • +
    • + + Encourage validating assumptions early, getting feedback from users early and frequently +
    • +
    • + + Encourage thinking about your customers to ensure product market fit +
    • +
    • + + Encourage building iteratively, fail fast and course correct, "smallest possible thing + to validate the most important risks" +
    • +
    • + + Recommend projects figure out how measure their impact (as a definition of success), + and track it +
    • +
    +
    +
    +

    We don't

    +
      +
    • + + "Build it and they will come" +
    • +
    • + + Perfect products before launching them +
    • +
    • + + Grow projects that have no clear strategy/route to impact +
    • +
    • + + Invest time and resources in projects beyond our tech/Palestine remit, though we do encourage + tech/Palestine projects to include non-Palestine initiatives where it makes sense +
    • +
    • + + Invest time and resources in projects beyond our capability (Healthcare apps for example) +
    • +
    • + + Shy away from communicating facts and realities, even if they are difficult to digest +
    • +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +

    + Ready to make a difference? Apply to become a mentor and help shape the next generation + of Palestinian tech projects. +

    + +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + diff --git a/src/pages/privacy-policy-new.astro b/src/pages/privacy-policy-new.astro new file mode 100644 index 00000000..8c054077 --- /dev/null +++ b/src/pages/privacy-policy-new.astro @@ -0,0 +1,405 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + +
    + + +
    +
    +
    +

    Legal · Tech for Palestine

    +

    + Privacy Policy +

    +

    + Last updated August 5, 2025. How we collect, use, and protect your information. +

    +
    +
    +
    + +
    +

    + This Privacy Policy describes how Tech for Palestine (a 501(c)(3) nonprofit, referred to as "we," "us," or "our") collects, uses, and shares personal information when you use our services (the "Services"). The Services include: +

    +
      +
    • + + Visiting our website: including all pages on our site (e.g. techforpalestine.org) that link to this Privacy Policy. +
    • +
    • + + Engaging with our organization: for example, joining our mailing list, signing up to volunteer, or submitting a project application to our incubator program via online forms. +
    • +
    • + + Using our application: if your project is accepted into our incubator, using our online portal or app to update project information and collaborate with us. +
    • +
    +

    + Please read this Privacy Policy carefully. By using our Services, you agree to the collection and use of information as described in this policy. If you have any questions, see the Contact Us section below. +

    +
    + +
    +

    1. Information We Collect

    +

    + We collect several types of information from and about users of our Services, including personal information. The exact data we collect depends on how you interact with us. +

    + +

    Information You Provide to Us

    +

    + We collect personal information that you choose to provide directly, such as through forms or communications. This includes: +

    +
      +
    • + + Contact Information: Your name, email address, telephone number, and similar contact data that you provide when, for example, joining our mailing list or signing up to volunteer. +
    • +
    • + + Volunteer or Application Information: Details you provide when you volunteer or apply to our incubator, such as your address, professional or educational background, project proposal, and any other information you include on application or registration forms. +
    • +
    • + + Communications: The content of messages or inquiries you send us, such as emails, survey responses, or feedback. This may include personal data if you include it in those communications. +
    • +
    • + + Other Information You Choose to Provide: Any other data you voluntarily provide, such as when responding to surveys, participating in events, or other interactions. +
    • +
    +
    +

    + Note: If you provide information about others (for example, refer a friend or include team members on a project application), you are responsible for ensuring you have the right or consent to share that information with us. +

    +
    + +

    Information We Collect Automatically

    +

    + When you use our website or app, we automatically collect certain information about your device and usage via cookies and other tracking technologies. This includes: +

    +
      +
    • + + Device and Technical Data: IP address, browser type, device type, operating system, network information, and device identifiers. +
    • +
    • + + Usage Data: Details about how you use our Services, such as pages or screens viewed, links clicked, time spent, and navigation paths. We may log dates and times of visits, and performance data for our website or app. +
    • +
    • + + Cookies and Similar Technologies: We (and authorized third parties) use cookies, web beacons, and similar tracking technologies to collect information about your interactions. See Cookies and Tracking Technologies below. +
    • +
    + +

    Information from Third Parties

    +
      +
    • + + Service Providers: For example, if we use an email newsletter platform or a volunteer management tool, it might provide us with information such as confirmation that you opened our email or completed a form. +
    • +
    • + + Social Media or Linked Services: If you engage with our organization on social media platforms, those platforms may share limited information with us according to their policies. +
    • +
    • + + Partner Organizations: We may obtain information from partner nonprofits or collaborators. We handle such data according to this Policy. +
    • +
    • + + Public Sources: We might collect information from publicly available sources for outreach or research purposes. +
    • +
    +
    + +
    +

    2. How We Use Your Information

    +

    + We use personal information for purposes consistent with our nonprofit mission and as necessary to provide our Services: +

    +
      +
    • + + Providing and Improving Services: To operate and maintain our website, applications, and programs. For example, we use personal data to process volunteer registrations, evaluate incubator project applications, and facilitate project incubator activities. +
    • +
    • + + Communication: To communicate with you about your involvement or interest in our organization, including responding to inquiries, sending confirmations, and notifying you of important changes. +
    • +
    • + + Email Newsletters and Updates: If you join our mailing list or opt in, to send you newsletters, announcements, event invitations, or fundraising appeals. You can always opt out as described in Your Privacy Rights. +
    • +
    • + + Volunteer and Donor Management: To manage volunteer assignments or donor relationships, such as tracking volunteer hours or sending donation receipts. +
    • +
    • + + Incubator Program Support: For accepted projects, to provide services such as mentorship, access to resources, and tracking of project progress. +
    • +
    • + + Analytics and Improvements: To analyze usage of our website and app to understand engagement, troubleshoot issues, and make informed decisions about improvements. +
    • +
    • + + Legal and Security: To protect the rights and safety of our organization, our users, or others, and to comply with applicable laws and regulations. +
    • +
    • + + Record-Keeping: To maintain proper organizational records for administrative purposes, including fulfilling any legal record retention requirements. +
    • +
    • + + Other Purposes with Consent: If we intend to use your information for a purpose not described above, we will explain it at the time of collection and, if required by law, obtain your consent. +
    • +
    +
    + +
    +

    3. How We Share Your Information

    +

    + We do not sell your personal information to third parties for profit or monetary gain. We only share or disclose personal data in the following circumstances: +

    +
      +
    • + + Service Providers: We may share information with third-party vendors who perform services on our behalf (hosting, email platforms, volunteer databases, payment processors). They are contractually obligated to protect it and use it only for our authorized purposes. +
    • +
    • + + Partner Organizations and Incubator Advisors: If you are accepted into our incubator program, we might share relevant information with mentors or advisors strictly to further the support of your project, with your consent or under strict confidentiality agreements. +
    • +
    • + + Legal Compliance and Protection: We may disclose information if required by law, to comply with court orders or government requests, or to protect the safety of our users or the public. +
    • +
    • + + Organizational Transfers: In the unlikely event of a major organizational change such as a merger with another nonprofit, personal information may be transferred to the successor entity and would remain subject to the promises made in this Privacy Policy. +
    • +
    • + + With Your Consent: We may share your personal information for other purposes if you ask us to or explicitly consent to such sharing. +
    • +
    • + + Aggregated or De-Identified Data: We may share information that has been aggregated or de-identified so it can no longer be linked back to you. +
    • +
    +

    + We do not share personal information with third parties for their own marketing or advertising purposes without your consent. We also do not "sell" or "share" personal information as defined under the California Consumer Privacy Act (CCPA). +

    +
    + +
    +

    4. Cookies and Tracking Technologies

    +

    + Like most websites and apps, we use cookies and similar tracking technologies to provide and improve our Services. We use cookies for several reasons: +

    +
      +
    • + + Essential Cookies: Some cookies are necessary for our site to function properly, for example to keep you logged in to the incubator portal or to remember your preferences. +
    • +
    • + + Analytics Cookies: We use third-party analytics tools (such as Google Analytics) that set cookies to collect information about how users navigate our site. This helps us understand usage patterns and make our website more user-friendly. +
    • +
    • + + Functional Cookies: These cookies remember choices you make (such as your language or region, if applicable) and enhance your experience. +
    • +
    • + + Third-Party and Social Media Cookies: If our site integrates content from third parties (for example, an embedded video or social media button), those providers may set cookies governed by their own privacy policies. +
    • +
    +

    + Your choices: On your first visit, you may see a cookies notice or banner. You can manage cookies through your browser settings — most browsers allow you to refuse new cookies, remove existing cookies, or be notified when new cookies are set. Note that blocking cookies may affect some features of our Services, especially the incubator app login. +

    +

    + We currently do not use online advertising networks or behavioral advertising, so you should not see third-party ads on our site that track you. +

    +
    + +
    +

    5. Data Security

    +

    + We take data security seriously and have implemented reasonable security measures to protect your personal information from unauthorized access, disclosure, alteration, and destruction. These include technical, administrative, and physical safeguards appropriate to the sensitivity of the information. For example, our website uses HTTPS encryption to secure data in transit, and we restrict access to personal data to staff or volunteers who need it. +

    +

    + Please understand that no method of transmission over the Internet or electronic storage is 100% secure. While we strive to protect your personal information, we cannot guarantee absolute security. In the event of a data breach that affects your personal information, we will notify you and the appropriate authorities as required by law. +

    +

    + We encourage you to take steps to protect your own information — such as choosing a strong password for any account in our incubator app and keeping your login credentials confidential. +

    +
    + +
    +

    6. Data Retention

    +

    + We will retain your personal information only for as long as necessary to fulfill the purposes we collected it for, including to satisfy any legal, accounting, or reporting requirements. +

    +
      +
    • + + Active Use: Information you provide as a volunteer or program participant will be kept for as long as you are active with our organization. +
    • +
    • + + Mailing List: If you subscribe to our newsletter, we will retain your email address and related preferences until you unsubscribe or ask us to delete that information. +
    • +
    • + + Applications and Records: If you submit a project application that is not accepted, we may keep the information only as long as necessary for legitimate nonprofit purposes or legal compliance. +
    • +
    • + + Legal Obligations: We may need to retain certain data for longer periods if required by law — for example, donation transaction records for 7 years for tax and audit purposes. +
    • +
    • + + Backup and Archival: Even after deletion, copies of your data may remain in our backups for a brief period until those backups are cycled out. We aim to incorporate secure deletion practices where possible. +
    • +
    +
    + +
    +

    7. Children's Privacy

    +

    + Our Services are not intended for children under the age of 13, and we do not knowingly collect personal information from children under 13 years old without parental consent. If you are under 13, please do not use our website or send us any personal information without your parent or guardian's permission. +

    +

    + If we learn that we have inadvertently collected personal data from a child under 13 without parental consent, we will take steps to delete that information as soon as possible. Please contact us immediately if you believe we might have any information from or about a child under 13. +

    +

    + For minors age 13 to 17: we generally require that individuals be at least 18 to volunteer or participate in our programs without a guardian. We strongly encourage parents and guardians to educate their children about online safety and to supervise their children's use of websites. +

    +
    + +
    +

    8. Your Privacy Rights

    +

    + Depending on your jurisdiction, you may have certain legal rights regarding your personal information. We strive to honor these rights and provide transparency and control to all our users. +

    + +

    General Rights for All Users

    +
      +
    • + + Access and Update: You have the right to request access to the personal information we hold about you and to request correction or updates if it is inaccurate or incomplete. +
    • +
    • + + Deletion: You can ask us to delete your personal information. We will honor deletion requests to the extent required by law and technically feasible. We may retain certain information for legal compliance as described above. +
    • +
    • + + Objection to Processing: If we process your information based on our legitimate interests, you can object to that processing if you feel it impacts your fundamental rights and freedoms. +
    • +
    • + + Opt-Out of Marketing: If at any time you prefer not to receive our newsletter or other marketing emails, you can click the "unsubscribe" link in any promotional email or contact us directly. We will stop sending you non-transactional communications once we process your request. +
    • +
    • + + Consent Withdrawal: If we are processing your personal information based on your consent, you have the right to withdraw that consent at any time. Withdrawal will not affect processing already carried out. +
    • +
    • + + Non-Discrimination: We will not discriminate against you for exercising any of these rights. +
    • +
    +

    + To exercise any of the above rights, see the Contact Us section below. We may need to verify your identity before fulfilling certain requests, and will respond within a reasonable timeframe in accordance with applicable law. +

    +
    + +
    +

    9. Changes to this Privacy Policy

    +

    + We may update or revise this Privacy Policy from time to time to reflect changes in our practices, technologies, legal requirements, or for other reasons. When we do, we will change the "Last updated" date at the top of this Policy. The updated Privacy Policy will be effective as of the time of posting, or a later date if explicitly stated. +

    +

    + If we make any material changes to this Privacy Policy, we will provide a more prominent notice — such as posting a notice on our website or contacting you via email. Your continued use of the Services after any changes indicates your acknowledgment of the updated Privacy Policy. +

    +
    + +
    +

    10. Contact Us

    +

    + If you have any questions, concerns, or requests regarding this Privacy Policy or how we handle your personal information, please contact us: +

    +
    +

    Tech for Palestine (Attn: Privacy)

    +

    548 Market St #266950

    +

    San Francisco, California 94104-5401

    +

    United States

    +

    + Email:{" "} + contact@techforpalestine.org + (include "Privacy Inquiry" in the subject) +

    +
    +

    + We will be happy to respond to your inquiries or assist you in exercising your rights. For our terms of service, see our Terms & Conditions. For our legal and financial information, see our Financial & Legal Notice. Thank you for reading this policy. +

    +
    + +
    +
    +
    + +
    +
    + + + + diff --git a/src/pages/projects-new.astro b/src/pages/projects-new.astro new file mode 100644 index 00000000..c05c5209 --- /dev/null +++ b/src/pages/projects-new.astro @@ -0,0 +1,123 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import ProjectsDirectory from "../components/projects/ProjectsDirectory"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + + +
    + +
    +
    +
    +
    +

    Incubator portfolio

    +

    + 80+ projects building toward a free Palestine +

    +

    + Built by tech workers, advocates, and builders who refuse to stay silent. + Browse them, support them, or apply to bring yours. +

    +
    +
    + +
    +
    + +
    +
    +
    + + +
    + +
    + + +
    +
    +
    +
    +

    Ready to build?

    +

    + Have a project that supports Palestine? +

    +

    + T4P connects you with mentors, partners, and a community of 10,000+ advocates. +

    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + + diff --git a/src/pages/team-new.astro b/src/pages/team-new.astro new file mode 100644 index 00000000..08762146 --- /dev/null +++ b/src/pages/team-new.astro @@ -0,0 +1,178 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const board = [ + { + name: "Paul Biggar", + role: "Founder & CEO", + image: "/paul-biggar.png", + bio: "Paul Biggar leads Tech for Palestine, a collaboration of tech projects advocating for Palestine. At its core, Tech for Palestine is an incubator to start and support projects for Palestine with mentorship, tech volunteers, marketing support, and connections to the broad Palestine advocacy community. He previously founded tech startups Darklang and CircleCI, as well as a Y Combinator-backed startup. He graduated with a PhD in Computer Science from Trinity College Dublin in 2010.", + }, + { + name: "Amir Nathoo", + role: "Co-founder & CEO of Outschool", + image: "/amir-nathoo.jpg", + imageLeft: true, + bio: "Amir seeks technology and social levers to disrupt the status quo and drive positive change at scale. He is Co-founder and CEO of Outschool, an education marketplace for kids, having previously founded Trigger.io, a development platform for creating native mobile apps, and worked at Square, leading the development of Square Payroll. He holds an MEng in Electrical and Information Sciences from the University of Cambridge. Since late 2023, Amir has led, funded and supported a variety of humanitarian, political and economic projects in resistance to Israel's genocide and in support of Palestinian liberation. He provides free classes for Palestinian refugee kids through Outschool.org's non profit initiative and is an investor in Boycat.io amongst others.", + }, +]; + +const staff = [ + { name: "Afzal Mangal", role: "Director of Marketing", image: "/images/staff/afzal.jpg" }, + { name: "Alaa' Odeh", role: "Director of Operations (Interim)", image: "/images/staff/alaa.jpg", objectPosition: "center 20%" }, + { name: "Azza Harara", role: "Director of Volunteering and Recruitment", image: "/images/staff/azza.jpg" }, + { name: "Cecile Potier", role: "Volunteers and Members Coordinator", image: "/images/staff/cecile.jpeg" }, + { name: "Iva Gumnishka", role: "Director of Strategic Partnerships & Events", image: "/images/staff/iva.jpg", objectPosition: "center top" }, + { name: "Kate Kerpez", role: "Director of Development", image: "/images/staff/kate.png", objectPosition: "center top" }, + { name: "Omar Hamayel", role: "Director of Finance", image: "/images/staff/omar.JPG" }, + { name: "Samira Hayat", role: "Project & Teams Operations Manager", image: "/images/staff/samira.jpg" }, + { name: "Tom Hall", role: "Director of Incubation", image: "/images/staff/tom.jpg", objectPosition: "center 43%" }, + { name: "Zahid Hanif", role: "Director of Engineering", image: "/images/staff/zahid.jpeg" }, +]; +--- + + + +
    + + +
    +
    +
    +

    Our Team

    +

    + The people building tech for Palestinian freedom and liberation +

    +

    + Meet the dedicated leaders guiding Tech for Palestine's mission. +

    +
    +
    +
    + + +
    +
    +

    Board of Directors

    +
    + {board.map((member) => ( +
    + {member.imageLeft && ( + + )} +
    +

    {member.name}

    +

    {member.role}

    +

    {member.bio}

    +
    + {!member.imageLeft && ( + + )} +
    + ))} +
    +
    +
    + + +
    +
    +

    Our Staff

    +
    + {staff.map((member) => ( +
    +
    + {member.name} +
    +

    {member.name}

    +

    {member.role}

    +
    + ))} +
    +
    +
    + + +
    +
    + +
    +
    + +
    +
    + + + + diff --git a/src/pages/terms-new.astro b/src/pages/terms-new.astro new file mode 100644 index 00000000..fd73650f --- /dev/null +++ b/src/pages/terms-new.astro @@ -0,0 +1,432 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + +
    + + +
    +
    +
    +

    Legal · Tech for Palestine

    +

    + Terms & Conditions +

    +

    + Last updated August 5, 2025. By using our services, you agree to these terms. +

    +
    +
    +
    + +
    +

    + Welcome to the Tech for Palestine website and services. Tech for Palestine ("we", "us", or "our") is a 501(c)(3) nonprofit organization whose mission is to harness technology and human innovation for a free and truly sovereign Palestine. These Terms of Service ("Terms") govern your access to and use of our website, our incubator application/portal, and any related services (collectively, the "Services"). +

    +

    + By accessing or using our Services, you agree to be bound by these Terms as well as our Privacy Policy (which is incorporated herein by reference). If you do not agree with any part of these Terms or our Privacy Policy, you must not use our Services. +

    +

    + Please read these Terms carefully. They constitute a legally binding agreement between you (the user of our Services) and Tech for Palestine. If you are using the Services on behalf of an organization or entity, you represent that you have the authority to bind that organization to these Terms. +

    +
    + +
    +

    1. Acceptance of Terms

    +

    + By using our website or any Services, you indicate that you accept these Terms and agree to comply with them. This acceptance is effective as of the date you first access the Services. If you do not agree, you must not use or access the Services. +

    +

    + We may also ask you to signify agreement to these Terms by clicking "Accept" or "Agree" (for example, when registering an account). Even if we do not do so, your continued use of the Services constitutes acceptance. +

    +

    + Supplemental terms: In certain cases, additional terms or guidelines may apply to specific features or activities. Any supplemental terms will be disclosed to you in connection with the relevant Service and are incorporated into these Terms by reference. In the event of a conflict, the supplemental terms will prevail for the specific service they apply to. +

    +
    + +
    +

    2. Eligibility

    +

    + The Services are offered and available only to users who are at least 13 years of age. By using the Services, you represent and warrant that you are 13 or older. If you are under 13 years old, you are not permitted to use our Services. +

    +

    + If you are between 13 and 17 years of age, you may use the Services only with the involvement and consent of a parent or legal guardian. We reserve the right to request verifiable parental consent for any users under 18 if necessary. +

    +

    + Users must also have the power to enter a binding contract with us and are not barred from doing so under any applicable laws. +

    +

    + The Services are provided from the United States. If you access the Services from outside the U.S., you are responsible for ensuring your use is lawful in your jurisdiction and complying with local laws. +

    +
    + +
    +

    3. User Accounts and Registration

    +

    + 3.1 Account Creation: Some parts of our Services may require you to create an account. If you register, you agree to provide accurate, current, and complete information and to update it if it changes. +

    +

    + 3.2 Account Security: You are responsible for maintaining the confidentiality of your account credentials. You must not share your password with others. Notify us immediately at contact@techforpalestine.org if you suspect unauthorized use of your account. +

    +

    + 3.3 User Responsibilities: When you create an account, you agree to: +

    +
      +
    • + + Use the account solely for your personal or organizational use related to our Services, and not impersonate any other person or entity. +
    • +
    • + + Not select or use a username that is offensive, obscene, or infringes someone's rights. +
    • +
    • + + Ensure all information you provide is truthful and lawful. +
    • +
    • + + If you provide personal data of others, ensure you have obtained their consent to share that information with us. +
    • +
    +
    + +
    +

    4. Acceptable Use Policy

    +

    + You agree to use our Services only for lawful purposes and in compliance with these Terms. You are solely responsible for your conduct and any content you submit. You agree not to engage in any of the following prohibited activities: +

    +
      +
    • + + Illegal Activities: Using the Services for any purpose that violates any applicable law or regulation, including fraud, hacking, or trafficking of unlawful material. +
    • +
    • + + Abusive or Harmful Conduct: Uploading or transmitting content that advocates violence or is discriminatory, abusive, threatening, harassing, defamatory, obscene, or otherwise offensive. +
    • +
    • + + Endangering Any Person: Stalking, intimidating, harming, or endangering another person, or sharing another person's private information without consent (doxxing). +
    • +
    • + + Security Violations: Attempting to probe or test the vulnerability of our systems, or introducing viruses, malware, or other harmful code. +
    • +
    • + + Unauthorized Access: Accessing non-public areas of the Services or attempting to gain unauthorized access to other users' accounts. +
    • +
    • + + Spam and Misuse: Sending unsolicited bulk communications or using automated means (bots, scrapers) to collect data without our express permission. +
    • +
    • + + Commercial or Political Use: Using the Services for commercial solicitation or advertising not approved by us, or promoting political campaigns in a way that implies our endorsement. +
    • +
    • + + Impersonation: Impersonating any person or entity, or falsely implying affiliation with us. +
    • +
    • + + Disruption: Engaging in any conduct that restricts or inhibits anyone's use or enjoyment of the Services. +
    • +
    +

    + Violation of the above may result in immediate termination or suspension of your account or access to the Services, and may also expose you to civil or criminal penalties. +

    +
    + +
    +

    5. User Content and Submissions

    +

    + Our Services may allow you to submit or upload content — for example, project information in our incubator portal, application materials, or forum communications. Any content a user submits is referred to as "User Content." +

    +

    + 5.1 Your Responsibility: You retain any intellectual property rights you hold in the User Content you provide. We do not claim ownership of your original content. However, by providing User Content, you grant us certain rights to use it as described below. You affirm that: +

    +
      +
    • + + You own or have necessary rights to submit the content and to grant the licenses to us under these Terms. +
    • +
    • + + Your User Content will not infringe or violate any third party's intellectual property, privacy, or other rights, nor violate any law. +
    • +
    • + + Your content is not defamatory, obscene, hateful, or otherwise violative of our Acceptable Use Policy (Section 4). +
    • +
    +

    + 5.2 License Grant: By submitting User Content, you grant Tech for Palestine a non-exclusive, worldwide, royalty-free license to use, reproduce, distribute, and display your content solely for the purpose of operating and providing the Services and fulfilling our mission. We will not use your content for commercial purposes unrelated to our nonprofit mission, and we will not sell your content to third parties. +

    +

    + 5.3 No Compensation: Tech for Palestine is not obligated to provide any compensation for User Content. Any contribution of content is purely voluntary. +

    +

    + 5.4 Monitoring: We reserve the right to review, screen, or delete any User Content at any time, including if we believe it violates these Terms. +

    +

    + 5.5 Backups: You are encouraged to keep backups of your own content. We are not responsible for lost or unrecoverable User Content. +

    +

    + 5.6 Feedback: If you provide suggestions or feedback about the Services, you agree that we can use and share such feedback for any purpose without compensation to you. +

    +
    + +
    +

    6. Intellectual Property Rights

    +

    + 6.1 Our Content: Unless otherwise indicated, the Services and all content therein — including text, graphics, logos, images, software code, audio/video clips, and documentation — are the property of Tech for Palestine or our licensors, protected by U.S. and international copyright and trademark laws. +

    +
      +
    • + + Our name, logo, and all related marks are trademarks of Tech for Palestine. You are not granted any license to use these marks without our prior written permission. +
    • +
    • + + The look and feel of the website and app are part of our branding and are also protected. +
    • +
    +

    + 6.2 License to You: We grant you a limited, non-exclusive, non-transferable, revocable license to access and use the Services for your personal, non-commercial use in accordance with these Terms. Under this license, you must not copy, modify, distribute, publish, or create derivative works of our content without prior consent, nor use our trademarks without authorization. +

    +

    + 6.3 User Content: These Terms do not grant us ownership of your User Content (see Section 5). However, all User Content is subject to the license you granted us and may be removed if it infringes rights or violates these Terms. +

    +

    + 6.4 DMCA Copyright Policy: If you believe content on our Services infringes your copyright, please notify us at: Tech for Palestine, 548 Market St #266950, San Francisco, CA 94104-5401, contact@techforpalestine.org (label "DMCA Notice"). Your notice must include a description of the copyrighted work, location of the material, your contact information, a good faith belief statement, an accuracy statement under penalty of perjury, and your signature. +

    +
    + + + +
    +

    8. Termination and Suspension

    +

    + 8.1 By You: You may stop using our Services at any time. If you have an account, you can request deletion by contacting us. +

    +

    + 8.2 By Us: We reserve the right to suspend or terminate your access at our sole discretion if we believe you have violated these Terms, created legal exposure for us, or for other operational reasons. We will provide reasonable notice when feasible, except where immediate action is necessary. +

    +

    + 8.3 Effect of Termination: Upon termination, your right to use the Services will immediately cease. We may delete your account data in accordance with our Privacy Policy. Provisions of these Terms that by their nature should survive termination — including Intellectual Property, Disclaimers, Limitation of Liability, and Indemnification — will remain in effect. +

    +
    + +
    +

    9. Disclaimers of Warranties

    +

    + Tech for Palestine provides the Services on an "AS IS" and "AS AVAILABLE" basis. To the fullest extent permitted by law, we disclaim all warranties of any kind, whether express, implied, or statutory. We make no guarantees that: +

    +
      +
    • + + The Services will always be available, timely, secure, uninterrupted, or error-free. +
    • +
    • + + The results of using the Services will meet your expectations or requirements. +
    • +
    • + + Any information or content obtained through the Services will be accurate, complete, or reliable. +
    • +
    +

    + We expressly disclaim all implied warranties, including merchantability, fitness for a particular purpose, title, and non-infringement. Some jurisdictions do not allow the exclusion of certain warranties; if those laws apply to you, some exclusions may not apply. +

    +
    + +
    +

    10. Limitation of Liability

    +

    + To the fullest extent permitted by law, Tech for Palestine and its officers, directors, employees, volunteers, agents, partners, and affiliates will not be liable for any indirect, incidental, special, consequential, exemplary, or punitive damages — including loss of profits, loss of data, service interruption, or the cost of substitute services — arising from your use of, or inability to use, the Services. +

    +

    + This limitation covers damages resulting from, among other things: +

    +
      +
    • + + Your use of or reliance on any content or information obtained through the Services. +
    • +
    • + + Unauthorized access to or alteration of your transmissions or data. +
    • +
    • + + Statements or conduct of any third party on the Services. +
    • +
    • + + Any transactions or relationships between you and any third party in connection with the Services. +
    • +
    +

    + Cap on Liability: In no event shall total liability exceed the greater of (a) $100 USD, or (b) the total amount of fees you paid to us for use of the Services in the 6 months prior to the claim. Some jurisdictions do not allow certain limitations, so some of the above may not apply to you. +

    +
    + +
    +

    11. Indemnification

    +

    + To the extent permitted by law, you agree to indemnify, defend, and hold harmless Tech for Palestine and its affiliates, officers, directors, employees, agents, and volunteers from and against any claims, liabilities, damages, losses, costs, or fees (including reasonable attorneys' fees) arising out of or relating to: +

    +
      +
    • + + Your willful misuse of the Services, or unlawful activities in connection with the Services. +
    • +
    • + + Any User Content you submit that infringes third-party rights or causes harm. +
    • +
    • + + Your willful violation of these Terms or applicable laws. +
    • +
    • + + Unauthorized use of the Services with your credentials resulting from your failure to maintain their confidentiality. +
    • +
    +

    + This indemnification obligation survives any termination of your account or of the Services. +

    +
    + +
    +

    12. General Provisions

    +

    + 12.1 Governing Law: These Terms will be governed by the laws of the State of Georgia, USA, without giving effect to conflict of laws principles. +

    +

    + 12.2 Jurisdiction and Venue: Any judicial proceedings will be brought in federal or state courts located in Georgia, unless prohibited by law. We reserve the right to seek injunctive relief in any jurisdiction to protect our intellectual property. +

    +

    + 12.3 Alternative Dispute Resolution: Both parties are free to seek an amicable resolution. You agree to contact us first to try to resolve any dispute informally. +

    +

    + 12.4 No Class Actions: To the extent permitted by law, you and Tech for Palestine agree that each may bring claims only in an individual capacity and not as a plaintiff or class member in any class or representative action. +

    +

    + 12.5 No Waiver: Our failure to exercise or enforce any right shall not operate as a waiver of such right. +

    +

    + 12.6 Severability: If any provision of these Terms is held invalid or unenforceable, the remaining provisions will continue in full force and effect. +

    +

    + 12.7 Entire Agreement: These Terms, together with our Privacy Policy and any supplemental terms, constitute the entire agreement between you and Tech for Palestine regarding the Services. +

    +

    + 12.8 Assignment: You may not assign your rights under these Terms without our prior written consent. We may assign our rights in connection with a merger, acquisition, or sale of assets. +

    +

    + 12.9 Relationship of Parties: These Terms do not create any agency, partnership, joint venture, or employment relationship between you and Tech for Palestine. +

    +

    + 12.10 Force Majeure: Tech for Palestine will not be liable for failure or delay in performance caused by circumstances beyond our reasonable control, including acts of God, war, terrorism, pandemics, or power outages. +

    +

    + 12.11 Notices: We may provide notices via email, postal mail, or by posting on our website. Official notices to us should be sent to the contact address in Section 13. +

    +

    + 12.12 Export Controls: If you access our Services from outside the United States, you are responsible for compliance with local laws and applicable U.S. export control laws and regulations. +

    +

    + 12.13 Updates to Terms: We may occasionally update these Terms. If we make material changes, we will notify users by posting the updated Terms with a new effective date and/or by email notification. Your continued use of the Services after an update constitutes acceptance of the revised Terms. +

    +
    + +
    +

    13. Contact Information

    +

    + If you have any questions about these Terms of Service, you can reach us at: +

    +
    +

    Tech for Palestine

    +

    548 Market St #266950

    +

    San Francisco, California 94104-5401

    +

    United States

    +

    + Email:{" "} + contact@techforpalestine.org +

    +
    +

    + We value our community and welcome feedback. Thank you for being a part of our mission and for abiding by these Terms. +

    +
    + +
    +
    +
    + +
    +
    + + + + diff --git a/src/pages/tools-new.astro b/src/pages/tools-new.astro new file mode 100644 index 00000000..5e4e4b38 --- /dev/null +++ b/src/pages/tools-new.astro @@ -0,0 +1,277 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; + +const featured = [ + { + name: "Thaura", + desc: "A privacy-first AI assistant built by Syrian engineers — ethical AI that never trains on your data and stands with Palestine.", + link: "https://thaura.ai/", + logo: "/thaura.svg", + }, + { + name: "Newscord", + desc: "Get comprehensive news coverage with AI-powered insights from multiple sources in one place.", + link: "https://newscord.org/", + logo: "/newscord.svg", + }, + { + name: "Boycat", + desc: "Search brands, scan products, and make ethical shopping choices with confidence.", + link: "https://www.boycat.io/", + logo: "/Boycat.png", + }, + { + name: "Find A Protest", + desc: "Discover protests and actions across the world.", + link: "https://www.findaprotest.info/", + logo: "/findprotest.svg", + }, +]; + +const tools = [ + { + name: "UpScrolled", + desc: "Simply, social without limits.", + link: "https://upscrolled.com/en/", + logo: "/upscrolled-icon.svg", + }, + { + name: "T4P Datasets", + desc: "Tracking the human toll. Use the data from our APIs to tell their story.", + link: "https://data.techforpalestine.org/", + logo: "/mark-transparent.png", + }, + { + name: "The Wall", + desc: "A browser plugin to detect and block more than Israel related websites and their social accounts.", + link: "https://thewallproject.github.io/", + logo: "/thewall.svg", + }, + { + name: "Profile Pic Maker", + desc: "Wrap your profile pic in the colors of Palestine.", + link: "https://ppm.techforpalestine.org/", + logo: "/profile-maker.png", + }, + { + name: "Anti-Palestinian VC funds", + desc: "A database of anti-Palestinian VC funds.", + link: "https://github.com/TechForPalestine/antipalestinian-vc-funds/", + logo: "/mark-transparent.png", + }, + { + name: "Israeli Tech Alternatives", + desc: "Find alternatives to Israeli tech.", + link: "https://www.israelitechalternatives.com/", + logo: "/mark-transparent.png", + }, + { + name: "Jaywalk", + desc: "Stay safe and informed during protests with an interactive map.", + link: "https://jaywalkapp.org", + logo: "/jaywalk.png", + }, + { + name: "BuycatVPN", + desc: "Be safe on public Wi-Fi, block trackers, and browse freely.", + link: "https://www.boycat.io/vpn?utm_source=t4p&utm_medium=blog&utm_campaign=launch", + logo: "/Boycat.png", + }, + { + name: "UK University Repression League Table 2025", + desc: "See how UK universities rank in repressing activism and ties to arms industries.", + link: "https://si4j.org/universityrepression", + logo: "/mark-transparent.png", + }, + { + name: "Built with Apartheid", + desc: "Discover which products and technologies are built with Israeli apartheid.", + link: "https://builtwithapartheid.com/", + logo: "/mark-transparent.png", + }, + { + name: "Accountable Media", + desc: "Track media accountability and bias in coverage of Palestine.", + link: "https://accountable-media.com/", + logo: "/accountable-media.png", + }, +]; +--- + + + + +
    + +
    +
    +
    +

    Tools for the movement

    +

    + Tools that power the work +

    +

    + A growing list of tools built to support advocacy for Palestinian liberation. + Use them, share them, and help the movement reach further. +

    +
    + +
    +
    +
    + + +
    +
    +

    Featured

    + +
    +
    + + +
    +
    +

    More tools

    + +
    +
    + + +
    +
    +
    +
    +

    Building something?

    +

    + Building a tool for the movement? +

    +

    + Bring it to the Tech for Palestine Incubator and reach a community of 10,000+ advocates. +

    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/src/pages/volunteer-new.astro b/src/pages/volunteer-new.astro new file mode 100644 index 00000000..2be5c23a --- /dev/null +++ b/src/pages/volunteer-new.astro @@ -0,0 +1,327 @@ +--- +import { getEnv } from "../utils/getEnv"; +import HomeLayout from "../layouts/HomeLayout.astro"; +import HomeNavbar from "../components/home/HomeNavbar.astro"; +import Button from "../components/ui/Button.astro"; +import SignUpForm from "../structures/SignUpForm.astro"; + +const membershipLive = getEnv("MEMBERSHIP_LIVE", Astro.locals) === "true"; +--- + + + + +
    + +
    +
    +
    +

    Get Involved

    +

    + Join our Volunteer Community +

    +

    + Join us in volunteering to make a meaningful impact for Palestine. +

    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +

    Why join

    +

    Why Volunteer with Us?

    +
      +
    • + + Make a direct impact by applying your skills to projects that amplify Palestinian + voices and uplift the cause. +
    • +
    • + + Join a global network of technologists, creatives, entrepreneurs, professionals, and + activists united by a commitment to justice and human dignity for Palestinians. +
    • +
    • + + Develop professionally while working on meaningful initiatives that create lasting + positive change. +
    • +
    • + + Connect with like-minded individuals through community spaces, events, and + collaborative projects. +
    • +
    • + + Transform concern into concrete action with measurable outcomes for Palestinian + communities. +
    • +
    +
    +
    +
    + + +
    +
    +
    +

    How you can help

    +

    Volunteer Roles

    +
    +
    +

    Ongoing Volunteers

    +
      +
    • + + Regularly contribute your skills and collaborate with our project and/or T4P teams. +
    • +
    • + + Assignments are typically a minimum of 3 months. +
    • +
    +
    +
    +

    Subject Matter Expert & One-Off Engagements

    +
      +
    • + + Offer short-term expertise and consultation to support specific projects and/or + internal T4P teams. +
    • +
    • + + One-off short projects (eg. user-testing, website redesign/updates, data-entry). +
    • +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +

    Getting started

    +

    How to Join?

    +

    + Complete our{" "} + volunteer application form{" "} + to get matched with the right project or team—even if you're not sure how you want to + contribute!{" "} + Check out specific roles we're looking for right now! +

    +
    +
    +
    + + +
    +
    +
    +

    What to expect

    +

    Volunteer Expectations

    +
    +
    +

    What We Expect

    +
      +
    • + + A commitment of your time and expertise. +
    • +
    • + + Active collaboration and clear communication. +
    • +
    • + + Timely notification of any changes to your availability. +
    • +
    +
    +
    +

    What We Don't Expect

    +
      +
    • + + Daily or rigid meeting attendance, unless essential for your role. +
    • +
    • + + Exclusive commitment—you are welcome to engage in other initiatives. +
    • +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +

    Ready?

    +

    Next Steps

    +
      +
    1. + +
      +

      Submit your application

      +

      + Complete our{" "} + application form{" "} + to be matched with the right project or team. +

      +
      +
    2. +
    3. + +
      +

      Join our Discord

      +

      + Join our Discord server{" "} + and community calls to connect with the movement. +

      +
      +
    4. +
    5. + +
      +

      Stay connected

      +

      + Follow us on{" "} + Instagram,{" "} + Twitter,{" "} + LinkedIn,{" "} + YouTube,{" "} + GitHub,{" "} + Bluesky, + and more. +

      +
      +
    6. +
    +
    +
    +
    + + +
    +
    +
    +

    + Your time and skills can truly make a difference. Thank you for considering to volunteer + with us! +

    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + diff --git a/src/pages/volunteer.astro b/src/pages/volunteer.astro index 2f8811a3..e17962b3 100644 --- a/src/pages/volunteer.astro +++ b/src/pages/volunteer.astro @@ -8,28 +8,26 @@ import "../styles/base.css";
    -
    -

    - Join our Volunteer Community! -

    +
    +

    Join our Volunteer Community!

    Join us in volunteering to make a meaningful impact for Palestine.

    - -
    + +
    Apply to Volunteer Now Current Volunteer Needs @@ -37,62 +35,37 @@ import "../styles/base.css";
    -
    -
    +
    +
    -

    Why Volunteer with Us?

    -
      -
    • - Make a direct impact by applying your skills to projects that amplify Palestinian - voices and uplift the cause. -
    • -
    • - Join a global network of technologists, creatives, entrepreneurs, professionals, and - activists united by a commitment to justice and human dignity for Palestinians. -
    • -
    • - Develop professionally while working on meaningful initiatives that create lasting - positive change. -
    • -
    • - Connect with like-minded individuals through community spaces, events, and - collaborative projects. -
    • -
    • - Transform concern into concrete action with measurable outcomes for Palestinian - communities. -
    • +

      Why Volunteer with Us?

      +
        +
      • Make a direct impact by applying your skills to projects that amplify Palestinian voices and uplift the cause.
      • +
      • Join a global network of technologists, creatives, entrepreneurs, professionals, and activists united by a commitment to justice and human dignity for Palestinians.
      • +
      • Develop professionally while working on meaningful initiatives that create lasting positive change.
      • +
      • Connect with like-minded individuals through community spaces, events, and collaborative projects.
      • +
      • Transform concern into concrete action with measurable outcomes for Palestinian communities.
    -

    Volunteer Roles

    -
    -
    +

    Volunteer Roles

    +
    +

    Ongoing Volunteers

    -
      -
    • - Regularly contribute your skills and collaborate with our project and/or T4P - teams. -
    • +
        +
      • Regularly contribute your skills and collaborate with our project and/or T4P teams.
      • Assignments are typically a min of 3months+
    -
    -

    - Subject Matter Expert & One-Off Limited Engagements -

    -
      -
    • - Offer short-term expertise and consultation to support specific projects and/or - internal T4P teams. -
    • -
    • - One-off short projects (eg. user-testing, website redesign/updates, data-entry) -
    • +
      +

      Subject Matter Expert & One-Off Limited Engagements

      +
        +
      • Offer short-term expertise and consultation to support specific projects and/or internal T4P teams.
      • +
      • One-off short projects (eg. user-testing, website redesign/updates, data-entry)
    @@ -100,36 +73,26 @@ import "../styles/base.css";
    -

    How to Join?

    +

    How to Join?

    - Complete our volunteer application form to get matched with the right project or team—even if you’re not sure how you want to - contribute! - (Check out specific roles we're looking for right now!) -

    + Complete our volunteer application form to get matched with the right project or team—even if you're not sure how you want to contribute! (Check out specific roles we're looking for right now!)

    -

    Volunteer Expectations

    -
    +

    Volunteer Expectations

    +
    -

    What We Expect

    -
      +

      What We Expect

      +
      • A commitment of your time and expertise.
      • Active collaboration and clear communication.
      • Timely notification of any changes to your availability.
    -

    What We Don't Expect

    -
      +

      What We Don't Expect

      +
      • Daily or rigid meeting attendance, unless essential for your role.
      • Exclusive commitment—you are welcome to engage in other initiatives.
      @@ -139,20 +102,10 @@ import "../styles/base.css";
      -

      Next Steps

      -
        -
      • - Submit your application form. -
      • -
      • - Join our Discord server and community calls. -
      • +

        Next Steps

        +
          +
        • Submit your application form.
        • +
        • Join our Discord server and community calls.
        • Follow us on social media: @@ -166,21 +119,20 @@ import "../styles/base.css";

          - Your time and skills can truly make a difference. Thank you for considering to volunteer - with us! + Your time and skills can truly make a difference. Thank you for considering to volunteer with us!

          Apply to Volunteer Now Current Volunteer Needs diff --git a/src/styles/design-system.css b/src/styles/design-system.css new file mode 100644 index 00000000..97450c63 --- /dev/null +++ b/src/styles/design-system.css @@ -0,0 +1,297 @@ +/* Tech for Palestine Design System — Typography Scale + Three breakpoints: 390 (base) / 810 (sm) / 1200 (lg) + Fraunces for editorial, Outfit for UI. + Fonts are loaded via in the layout head. */ + +/* CSS custom property bridge — exposes design tokens to scoped