mirror of
https://github.com/FoggedLens/deflock.git
synced 2026-09-30 20:49:37 +02:00
Merge branch 'staging'
This commit is contained in:
commit
1ca6f1cff4
5 files changed
+464
-189
No files matched your search
@@ -0,0 +1,71 @@
|
|||||||
|
import { ref, readonly } from 'vue';
|
||||||
|
|
||||||
|
interface FeatureFlags {
|
||||||
|
iosApp: {
|
||||||
|
enabled: boolean;
|
||||||
|
appUrl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const flags = ref<FeatureFlags | null>(null);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
let fetchPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
|
async function fetchFeatureFlags(): Promise<void> {
|
||||||
|
if (flags.value !== null) {
|
||||||
|
// Already loaded
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fetchPromise) {
|
||||||
|
// Already fetching, wait for the existing promise
|
||||||
|
return fetchPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPromise = (async () => {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
const response = await fetch('https://cdn.deflock.me/config/flags.json');
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch feature flags: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
flags.value = data;
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Unknown error';
|
||||||
|
console.warn('Failed to load feature flags, using defaults:', error.value);
|
||||||
|
|
||||||
|
// Fallback to default values
|
||||||
|
flags.value = {
|
||||||
|
iosApp: {
|
||||||
|
enabled: false,
|
||||||
|
appUrl: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return fetchPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFeatureFlags() {
|
||||||
|
// Auto-fetch on first use
|
||||||
|
if (!fetchPromise) {
|
||||||
|
fetchFeatureFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
flags: readonly(flags),
|
||||||
|
isLoading: readonly(isLoading),
|
||||||
|
error: readonly(error),
|
||||||
|
refresh: fetchFeatureFlags
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,46 +1,128 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container class="mb-16 text-center">
|
<v-container class="mb-16">
|
||||||
<h1>Contact Us</h1>
|
<v-row justify="center" class="mb-8">
|
||||||
|
<v-col cols="12" md="8" class="text-center">
|
||||||
<div>
|
<h1 class="text-h3 font-weight-bold mb-4">Contact Us</h1>
|
||||||
<p>
|
<p class="text-h6 text-medium-emphasis">
|
||||||
Have questions about DeFlock? Need help contributing to OpenStreetMap?
|
How can we help you today?
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
Reach out using the contact options below.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-row class="mt-12">
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-card
|
|
||||||
variant="outlined"
|
|
||||||
append-icon="mdi-open-in-new"
|
|
||||||
class="mx-auto my-2"
|
|
||||||
href="mailto:contact@deflock.me"
|
|
||||||
max-width="344"
|
|
||||||
prepend-icon="mdi-email"
|
|
||||||
subtitle="contact@deflock.me"
|
|
||||||
target="_blank"
|
|
||||||
title="Email Us"
|
|
||||||
></v-card>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
<v-col cols="12" sm="6">
|
<v-row justify="center">
|
||||||
<v-card
|
<v-col cols="12" md="10" lg="8">
|
||||||
variant="outlined"
|
<v-row>
|
||||||
append-icon="mdi-open-in-new"
|
<!-- Report Camera Option -->
|
||||||
class="mx-auto my-2"
|
<v-col cols="12" md="6">
|
||||||
href="https://discord.gg/aV7v4R3sKT"
|
<v-card
|
||||||
max-width="344"
|
variant="outlined"
|
||||||
subtitle="discord.gg/aV7v4R3sKT"
|
class="mx-auto h-100 d-flex flex-column"
|
||||||
target="_blank"
|
max-width="400"
|
||||||
title="Join our Discord"
|
hover
|
||||||
>
|
@click="$router.push('/report')"
|
||||||
<template v-slot:prepend>
|
style="cursor: pointer;"
|
||||||
<v-img class="mr-2" contain width="24" height="24" :src="isDark ? 'icon-discord-white.svg' : 'icon-discord.svg'" />
|
>
|
||||||
</template>
|
<v-card-item class="text-center pa-6">
|
||||||
</v-card>
|
<v-icon
|
||||||
|
icon="mdi-camera"
|
||||||
|
size="48"
|
||||||
|
color="primary"
|
||||||
|
class="mb-4"
|
||||||
|
></v-icon>
|
||||||
|
<v-card-title class="text-h5 font-weight-bold card-title-wrap">
|
||||||
|
Report a Camera
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle class="text-body-1 mt-2 card-subtitle-wrap">
|
||||||
|
Found an ALPR camera? Help us map it and protect your community's privacy.
|
||||||
|
</v-card-subtitle>
|
||||||
|
</v-card-item>
|
||||||
|
<v-card-actions class="justify-center pb-6">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
append-icon="mdi-arrow-right"
|
||||||
|
>
|
||||||
|
Start Report
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- General Inquiry Option -->
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card
|
||||||
|
variant="outlined"
|
||||||
|
class="mx-auto h-100 d-flex flex-column"
|
||||||
|
max-width="400"
|
||||||
|
:hover="!showContactOptions"
|
||||||
|
@click="!showContactOptions ? showContactOptions = true : null"
|
||||||
|
:style="showContactOptions ? 'cursor: default;' : 'cursor: pointer;'"
|
||||||
|
>
|
||||||
|
<v-card-item class="text-center pa-6">
|
||||||
|
<v-icon
|
||||||
|
:icon="showContactOptions ? 'mdi-message-text' : 'mdi-message-text'"
|
||||||
|
size="48"
|
||||||
|
color="secondary"
|
||||||
|
class="mb-4"
|
||||||
|
></v-icon>
|
||||||
|
<v-card-title class="text-h5 font-weight-bold card-title-wrap">
|
||||||
|
{{ showContactOptions ? 'Get in Touch' : 'General Inquiry' }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle class="text-body-1 mt-2 card-subtitle-wrap">
|
||||||
|
{{ showContactOptions ? 'Send us an email with your questions' : 'Have questions about DeFlock, need help, or want to get involved?' }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
</v-card-item>
|
||||||
|
<v-card-actions class="justify-center pb-6">
|
||||||
|
<div v-if="!showContactOptions">
|
||||||
|
<v-btn
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
append-icon="mdi-arrow-right"
|
||||||
|
>
|
||||||
|
Contact Options
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else class="d-flex flex-column gap-3 w-100">
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-body-1 mb-2">Send us an email at:</p>
|
||||||
|
<a
|
||||||
|
href="mailto:contact@deflock.me"
|
||||||
|
class="text-h6 font-weight-bold text-decoration-none"
|
||||||
|
style="color: rgb(var(--v-theme-secondary));"
|
||||||
|
>
|
||||||
|
contact@deflock.me
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<p class="text-center text-body-2 text-medium-emphasis mt-8">
|
||||||
|
<b>Media inquiries?</b> Visit our
|
||||||
|
<router-link to="/press" class="text-decoration-none">
|
||||||
|
press page
|
||||||
|
</router-link>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Additional Help Section -->
|
||||||
|
<v-row justify="center">
|
||||||
|
<v-col cols="12" md="8" class="text-center">
|
||||||
|
<v-divider class="mb-6"></v-divider>
|
||||||
|
<p class="text-body-2 text-medium-emphasis">
|
||||||
|
Need to identify if something is an ALPR camera?<br>
|
||||||
|
<router-link to="/identify" class="text-decoration-none">
|
||||||
|
Check our camera gallery
|
||||||
|
</router-link>
|
||||||
|
or learn more about
|
||||||
|
<router-link to="/what-is-an-alpr" class="text-decoration-none">
|
||||||
|
what ALPRs are
|
||||||
|
</router-link>.
|
||||||
|
</p>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
@@ -51,13 +133,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Footer from '@/components/layout/Footer.vue';
|
import Footer from '@/components/layout/Footer.vue';
|
||||||
import { useTheme } from 'vuetify';
|
import { useTheme } from 'vuetify';
|
||||||
import { computed } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isDark = computed(() => theme.name.value === 'dark');
|
const isDark = computed(() => theme.name.value === 'dark');
|
||||||
|
const showContactOptions = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
@import url('@/assets/typography.css');
|
@import url('@/assets/typography.css');
|
||||||
|
|
||||||
|
.card-title-wrap {
|
||||||
|
white-space: normal !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
text-overflow: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-subtitle-wrap {
|
||||||
|
white-space: normal !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
text-overflow: unset !important;
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-3 > * + * {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
+204
-139
@@ -11,47 +11,188 @@
|
|||||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
||||||
<div class="text-center mb-8">
|
<div class="text-center mb-8">
|
||||||
<v-icon size="64" color="var(--df-blue)" class="mb-4">mdi-account-voice</v-icon>
|
<v-icon size="64" color="var(--df-blue)" class="mb-4">mdi-account-voice</v-icon>
|
||||||
<h2 class="text-h4 mb-4 font-weight-bold">Your Voice Matters at City Council</h2>
|
<h2 class="text-h4 mb-4 font-weight-bold">Your Voice Matters Locally</h2>
|
||||||
<p class="text-h6 text-medium-emphasis serif">
|
<p class="text-h6 text-medium-emphasis serif">
|
||||||
Public participation is the cornerstone of democracy. Here's how to effectively advocate
|
City council members rely on us to understand public opinion. Here's your step-by-step guide to effectively advocate
|
||||||
against ALPR surveillance systems at your local city council meetings.
|
against mass surveillance systems with your local representatives.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<!-- Quick Start Guide -->
|
<!-- Step 1: One-on-One Meetings -->
|
||||||
<v-row class="mb-12">
|
<v-row class="mb-12">
|
||||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
||||||
<v-card class="pa-6" elevation="3" rounded="lg">
|
<v-card class="pa-6" elevation="3" rounded="lg">
|
||||||
<div class="text-center mb-6">
|
<div class="d-flex align-center mb-4">
|
||||||
<v-icon size="48" color="success" class="mb-3">mdi-rocket-launch</v-icon>
|
<v-avatar size="48" color="primary" class="mr-4">
|
||||||
<h3 class="text-h5 font-weight-bold">Quick Start: 3 Steps to Success</h3>
|
<span class="text-h5 font-weight-bold white--text">1</span>
|
||||||
|
</v-avatar>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-h5 font-weight-bold mb-1">Meet Council Members Privately</h3>
|
||||||
|
<p class="text-body-2 text-medium-emphasis mb-0">Start with personal conversations - often the most effective approach</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p class="text-body-1 mb-4">
|
||||||
|
Personal conversations with council members are often the most effective way to influence their vote.
|
||||||
|
These private meetings allow for deeper discussion and help them understand the human impact of surveillance policies.
|
||||||
|
</p>
|
||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="4" class="text-center">
|
<v-col cols="12" md="6">
|
||||||
<v-avatar size="60" color="primary" class="mb-3">
|
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||||
<span class="text-h4 white--text">1</span>
|
<v-icon color="primary" class="mr-2">mdi-calendar-plus</v-icon>
|
||||||
</v-avatar>
|
How to Schedule
|
||||||
<h4 class="text-h6 mb-2">Research & Prepare</h4>
|
</h4>
|
||||||
<p class="text-body-2">Know the meeting schedule, agenda, and speaking rules</p>
|
<v-list density="compact">
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Contact their office via phone or email</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Suggest meeting at a local coffee shop or their office</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Request just 15-20 minutes of their time</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Mention you're a constituent concerned about ALPRs</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="4" class="text-center">
|
<v-col cols="12" md="6">
|
||||||
<v-avatar size="60" color="primary" class="mb-3">
|
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||||
<span class="text-h4 white--text">2</span>
|
<v-icon color="primary" class="mr-2">mdi-lightbulb-on</v-icon>
|
||||||
</v-avatar>
|
Meeting Tips
|
||||||
<h4 class="text-h6 mb-2">Craft Your Message</h4>
|
</h4>
|
||||||
<p class="text-body-2">Use our talking points and keep it within the time limit (usually around 3 minutes)</p>
|
<v-list density="compact">
|
||||||
</v-col>
|
<v-list-item prepend-icon="mdi-check">
|
||||||
<v-col cols="12" md="4" class="text-center">
|
<v-list-item-title>Bring a brief printed summary of key points</v-list-item-title>
|
||||||
<v-avatar size="60" color="primary" class="mb-3">
|
</v-list-item>
|
||||||
<span class="text-h4 white--text">3</span>
|
<v-list-item prepend-icon="mdi-check">
|
||||||
</v-avatar>
|
<v-list-item-title>Share personal concerns about privacy and community impact</v-list-item-title>
|
||||||
<h4 class="text-h6 mb-2">Speak & Follow Up</h4>
|
</v-list-item>
|
||||||
<p class="text-body-2">Deliver your message and engage with council members after the meeting</p>
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Ask about their position and listen to their concerns</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Respond to their concerns with your ideas</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 class="text-h6 font-weight-bold mb-2">Sample Email Template</h4>
|
||||||
|
<v-card class="pa-4" variant="tonal">
|
||||||
|
<p class="mb-0">
|
||||||
|
Hello [Council Member], I'm a [city] resident concerned about the upcoming
|
||||||
|
surveillance technology contract. Would you have 15 minutes to discuss this over coffee? I'd love to share some community
|
||||||
|
concerns and hear your thoughts.
|
||||||
|
</p>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Step 2: Public Speaking -->
|
||||||
|
<v-row class="mb-12">
|
||||||
|
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
||||||
|
<v-card class="pa-6" elevation="3" rounded="lg">
|
||||||
|
<div class="d-flex align-center mb-4">
|
||||||
|
<v-avatar size="48" color="primary" class="mr-4">
|
||||||
|
<span class="text-h5 font-weight-bold white--text">2</span>
|
||||||
|
</v-avatar>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-h5 font-weight-bold mb-1">Speak at Council Meetings</h3>
|
||||||
|
<p class="text-body-2 text-medium-emphasis mb-0">Create an official record and show public opposition</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-body-1 mb-4">
|
||||||
|
Public comment at council meetings creates an official record of community opposition and demonstrates
|
||||||
|
to council members that constituents are paying attention to their votes on surveillance issues.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||||
|
<v-icon color="primary" class="mr-2">mdi-clock</v-icon>
|
||||||
|
Before the Meeting
|
||||||
|
</h4>
|
||||||
|
<v-list density="compact">
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Check meeting schedule and agenda online</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Sign up for public comment (often required)</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Prepare 2-3 minute statement (practice timing)</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Bring a copy of your statement</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<h4 class="text-h6 mb-3 d-flex align-center">
|
||||||
|
<v-icon color="primary" class="mr-2">mdi-presentation</v-icon>
|
||||||
|
During the Meeting
|
||||||
|
</h4>
|
||||||
|
<v-list density="compact">
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Arrive on time or early</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>State your name and connection to the city</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Speak clearly and maintain eye contact</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item prepend-icon="mdi-check">
|
||||||
|
<v-list-item-title>Stay respectful and thank council for their time</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
|
|
||||||
|
<v-alert type="info" variant="tonal" class="mb-4">
|
||||||
|
<strong>Pro Tip:</strong> Focus on how ALPRs affect YOUR community specifically. Personal stories and local examples are more compelling than abstract arguments.
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<!-- Example Videos -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<h4 class="text-h6 font-weight-bold mb-4 d-flex align-center">
|
||||||
|
<v-icon color="primary" class="mr-2">mdi-video</v-icon>
|
||||||
|
Example Speeches
|
||||||
|
</h4>
|
||||||
|
<v-row>
|
||||||
|
<v-col v-for="video in videos" :key="video.url" cols="12" sm="6">
|
||||||
|
<v-card
|
||||||
|
class="video-card-compact pa-4 d-flex align-center"
|
||||||
|
elevation="2"
|
||||||
|
rounded="lg"
|
||||||
|
hover
|
||||||
|
@click="openVideo(video.url)"
|
||||||
|
>
|
||||||
|
<v-avatar size="48" color="primary" class="mr-3 video-play-button-compact">
|
||||||
|
<v-icon size="24" color="white">mdi-play</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h4 class="text-body-1 font-weight-bold mb-0">{{ video.location }}</h4>
|
||||||
|
<p class="text-caption text-medium-emphasis mb-0">City Council Meeting</p>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -59,14 +200,19 @@
|
|||||||
<!-- Success Stories -->
|
<!-- Success Stories -->
|
||||||
<v-row class="mb-12">
|
<v-row class="mb-12">
|
||||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
||||||
<v-card class="pa-6" elevation="3" rounded="lg" color="success" variant="tonal">
|
<v-card class="pa-6" elevation="3" rounded="lg" variant="tonal">
|
||||||
<div class="d-flex align-center justify-space-between mb-6">
|
<div class="d-flex align-center justify-space-between mb-6">
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<v-icon size="40" color="success" class="mr-3">mdi-trophy</v-icon>
|
<v-avatar size="48" color="primary" class="mr-4">
|
||||||
<h3 class="text-h5 font-weight-bold">Timeline of Recent Victories</h3>
|
<v-icon size="24" color="white">mdi-trophy</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-h5 font-weight-bold mb-1">Recent Victories</h3>
|
||||||
|
<p class="text-body-2 text-medium-emphasis mb-0">Communities across the country are winning</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<v-chip
|
<v-chip
|
||||||
color="success"
|
color="primary"
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
size="large"
|
size="large"
|
||||||
class="font-weight-bold"
|
class="font-weight-bold"
|
||||||
@@ -84,7 +230,7 @@
|
|||||||
:class="{ 'timeline-item-last': index === (showAllVictories ? citiesRejectingFlock.length - 1 : 2) }"
|
:class="{ 'timeline-item-last': index === (showAllVictories ? citiesRejectingFlock.length - 1 : 2) }"
|
||||||
>
|
>
|
||||||
<div class="timeline-marker">
|
<div class="timeline-marker">
|
||||||
<v-avatar size="48" color="success">
|
<v-avatar size="48" color="primary">
|
||||||
<v-icon color="white">mdi-check-bold</v-icon>
|
<v-icon color="white">mdi-check-bold</v-icon>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,7 +243,7 @@
|
|||||||
<p v-html="city.descriptionHtml" class="text-body-2 mb-2" />
|
<p v-html="city.descriptionHtml" class="text-body-2 mb-2" />
|
||||||
<v-chip
|
<v-chip
|
||||||
size="large"
|
size="large"
|
||||||
color="success"
|
color="primary"
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
>
|
>
|
||||||
<v-icon
|
<v-icon
|
||||||
@@ -121,7 +267,7 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
@click="showAllVictories = !showAllVictories"
|
@click="showAllVictories = !showAllVictories"
|
||||||
:prepend-icon="showAllVictories ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
:prepend-icon="showAllVictories ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
||||||
color="success"
|
color="primary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="large"
|
size="large"
|
||||||
class="font-weight-medium"
|
class="font-weight-medium"
|
||||||
@@ -134,71 +280,8 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<!-- CTA instead of talking points -->
|
|
||||||
|
|
||||||
<!-- First-Time Speaker Guide -->
|
|
||||||
<v-row class="mb-12">
|
|
||||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
|
||||||
<v-card class="pa-6" elevation="3" rounded="lg" color="info" variant="tonal">
|
|
||||||
<div class="d-flex align-center mb-6">
|
|
||||||
<v-icon size="40" color="info" class="mr-3">mdi-microphone</v-icon>
|
|
||||||
<h3 class="text-h5 font-weight-bold">First-Time Speaker's Guide</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<h4 class="text-h6 mb-3 d-flex align-center text">
|
|
||||||
<v-icon color="info" class="mr-2">mdi-clock</v-icon>
|
|
||||||
Before the Meeting
|
|
||||||
</h4>
|
|
||||||
<v-list density="compact">
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>Check meeting schedule and agenda online.</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>Sign up for public comment (often required).</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>Prepare 2-3 minute statement (practice timing).</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>Bring a copy of your statement.</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<h4 class="text-h6 mb-3 d-flex align-center">
|
|
||||||
<v-icon color="success" class="mr-2">mdi-presentation</v-icon>
|
|
||||||
During the Meeting
|
|
||||||
</h4>
|
|
||||||
<v-list density="compact">
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>Arrive on time or early.</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>State your name and connection to the city.</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>Speak clearly and maintain eye contact.</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item prepend-icon="mdi-check">
|
|
||||||
<v-list-item-title>Stay respectful, and thank council for their time.</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-divider class="my-4"></v-divider>
|
|
||||||
|
|
||||||
<v-alert type="info" variant="tonal" class="mb-4">
|
|
||||||
<strong>Pro Tip:</strong> Focus on how ALPRs affect YOUR community specifically. Personal stories and local examples are more compelling than abstract arguments.
|
|
||||||
</v-alert>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<!-- Community Support Section -->
|
<!-- Community Support Section -->
|
||||||
<v-row class="mb-12">
|
<v-row>
|
||||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
||||||
<v-card class="pa-6" elevation="3" rounded="lg" color="primary" variant="tonal">
|
<v-card class="pa-6" elevation="3" rounded="lg" color="primary" variant="tonal">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
@@ -222,38 +305,6 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<!-- Additional Resources -->
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" md="10" lg="8" class="mx-auto">
|
|
||||||
<v-card class="pa-6" elevation="3" rounded="lg">
|
|
||||||
<h3 class="text-h5 font-weight-bold mb-4 d-flex align-center">
|
|
||||||
<v-icon color="primary" class="mr-3">mdi-video</v-icon>
|
|
||||||
Example Videos
|
|
||||||
</h3>
|
|
||||||
<v-row>
|
|
||||||
<v-col v-for="video in videos" :key="video.url" cols="12" sm="6">
|
|
||||||
<v-card
|
|
||||||
class="video-card-compact pa-4 d-flex align-center"
|
|
||||||
elevation="2"
|
|
||||||
rounded="lg"
|
|
||||||
hover
|
|
||||||
@click="openVideo(video.url)"
|
|
||||||
>
|
|
||||||
<v-avatar size="48" color="primary" class="mr-3 video-play-button-compact">
|
|
||||||
<v-icon size="24" color="white">mdi-play</v-icon>
|
|
||||||
</v-avatar>
|
|
||||||
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h4 class="text-body-1 font-weight-bold mb-0">{{ video.location }}</h4>
|
|
||||||
<p class="text-caption text-medium-emphasis mb-0">City Council Meeting</p>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<Footer/>
|
<Footer/>
|
||||||
@@ -270,6 +321,8 @@ const isDark = computed(() => theme.name.value === 'dark');
|
|||||||
|
|
||||||
// Reactive variable for showing all victories
|
// Reactive variable for showing all victories
|
||||||
const showAllVictories = ref(false);
|
const showAllVictories = ref(false);
|
||||||
|
// Reactive variable for active tab
|
||||||
|
const activeTab = ref('step1');
|
||||||
|
|
||||||
enum Outcome {
|
enum Outcome {
|
||||||
ContractRejected = "Contract Rejected",
|
ContractRejected = "Contract Rejected",
|
||||||
@@ -372,6 +425,18 @@ const citiesRejectingFlock: CityRejection[] = [
|
|||||||
monthYear: "August 2025",
|
monthYear: "August 2025",
|
||||||
descriptionHtml: 'The Village of Scarsdale <a target="_blank" href="https://ij.org/press-release/public-interest-law-firm-applauds-westchester-county-village-for-ending-license-plate-reader-contract/">terminated its contract with Flock Safety</a> after over 450 community members signed a petition, expressing concerns over privacy and the system\'s data sharing practices.',
|
descriptionHtml: 'The Village of Scarsdale <a target="_blank" href="https://ij.org/press-release/public-interest-law-firm-applauds-westchester-county-village-for-ending-license-plate-reader-contract/">terminated its contract with Flock Safety</a> after over 450 community members signed a petition, expressing concerns over privacy and the system\'s data sharing practices.',
|
||||||
outcome: Outcome.ContractCanceled
|
outcome: Outcome.ContractCanceled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cityState: "Hays County, TX",
|
||||||
|
monthYear: "October 2025",
|
||||||
|
descriptionHtml: 'Hays County commissioners <a target=_blank" href="https://cbsaustin.com/news/local/hays-county-ends-contract-with-flock-cameras-over-privacy-concerns">voted 3-2 to terminate their contract with Flock Safety</a> after public concern for privacy and the risk of their personal information being exposed.',
|
||||||
|
outcome: Outcome.ContractCanceled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cityState: "Warrenton, VA",
|
||||||
|
monthYear: "October 2025",
|
||||||
|
descriptionHtml: 'The Town of Warrenton <a target="_blank" href="https://www.fauquiernow.com/news/not-on-our-watch-warrenton-town-council-reverses-course-on-license-plate-readers/article_9a1c02ac-5f77-47c7-a270-06f49ab98332.html">voted 5-1 to permanently block consideration of installing ALPRs in their town.</a>',
|
||||||
|
outcome: Outcome.Other
|
||||||
}
|
}
|
||||||
].sort((a, b) => {
|
].sort((a, b) => {
|
||||||
const [aMonth, aYear] = a.monthYear.split(/\s/);
|
const [aMonth, aYear] = a.monthYear.split(/\s/);
|
||||||
@@ -493,9 +558,9 @@ const openVideo = (url: string) => {
|
|||||||
bottom: 24px;
|
bottom: 24px;
|
||||||
width: 2px;
|
width: 2px;
|
||||||
background: linear-gradient(to bottom,
|
background: linear-gradient(to bottom,
|
||||||
rgba(76, 175, 80, 0.8) 0%,
|
rgba(var(--v-theme-primary), 0.8) 0%,
|
||||||
rgba(76, 175, 80, 0.6) 50%,
|
rgba(var(--v-theme-primary), 0.6) 50%,
|
||||||
rgba(76, 175, 80, 0.4) 100%);
|
rgba(var(--v-theme-primary), 0.4) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item {
|
.timeline-item {
|
||||||
@@ -523,26 +588,26 @@ const openVideo = (url: string) => {
|
|||||||
|
|
||||||
.timeline-card {
|
.timeline-card {
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
border: 1px solid rgba(var(--v-theme-primary), 0.2);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.1);
|
box-shadow: 0 2px 8px rgba(var(--v-theme-primary), 0.1);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-card:hover {
|
.timeline-card:hover {
|
||||||
box-shadow: 0 4px 16px rgba(76, 175, 80, 0.15);
|
box-shadow: 0 4px 16px rgba(var(--v-theme-primary), 0.15);
|
||||||
border-color: rgba(76, 175, 80, 0.3);
|
border-color: rgba(var(--v-theme-primary), 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark theme support */
|
/* Dark theme support */
|
||||||
.v-theme--dark .timeline-card {
|
.v-theme--dark .timeline-card {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
border-color: rgba(76, 175, 80, 0.3);
|
border-color: rgba(var(--v-theme-primary), 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-theme--dark .timeline-card:hover {
|
.v-theme--dark .timeline-card:hover {
|
||||||
border-color: rgba(76, 175, 80, 0.4);
|
border-color: rgba(var(--v-theme-primary), 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
@@ -40,6 +40,20 @@
|
|||||||
<span v-if="appLinks.android">Get on Android</span>
|
<span v-if="appLinks.android">Get on Android</span>
|
||||||
<span v-else>Android Coming Soon</span>
|
<span v-else>Android Coming Soon</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- Documentation Link -->
|
||||||
|
<v-btn
|
||||||
|
size="large"
|
||||||
|
variant="text"
|
||||||
|
color="white"
|
||||||
|
href="https://blog.deflock.me/deflock-mobile-guide/"
|
||||||
|
target="_blank"
|
||||||
|
prepend-icon="mdi-book-open-variant"
|
||||||
|
class="download-btn doc-btn"
|
||||||
|
>
|
||||||
|
View Mobile App Guide
|
||||||
|
<v-icon icon="mdi-open-in-new" size="small" class="ml-1" />
|
||||||
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -217,8 +231,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Footer from '@/components/layout/Footer.vue';
|
import Footer from '@/components/layout/Footer.vue';
|
||||||
|
import { useFeatureFlags } from '@/composables/useFeatureFlags';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
const { flags } = useFeatureFlags();
|
||||||
|
|
||||||
interface Feature {
|
interface Feature {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -252,10 +269,10 @@ interface PrivacyPrinciple {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const appLinks = {
|
const appLinks = computed(() => ({
|
||||||
android: 'https://play.google.com/store/apps/details?id=me.deflock.deflockapp',
|
android: 'https://play.google.com/store/apps/details?id=me.deflock.deflockapp',
|
||||||
ios: undefined,
|
ios: flags.value?.iosApp.enabled ? flags.value.iosApp.appUrl : undefined,
|
||||||
}
|
}));
|
||||||
|
|
||||||
// App features
|
// App features
|
||||||
const features: Feature[] = [
|
const features: Feature[] = [
|
||||||
@@ -475,6 +492,17 @@ const visibleScreenshots = computed(() =>
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.doc-btn {
|
||||||
|
color: white !important;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
.hero-image {
|
.hero-image {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -68,9 +68,11 @@
|
|||||||
<v-col cols="12" md="5" class="pa-4">
|
<v-col cols="12" md="5" class="pa-4">
|
||||||
<div class="app-card-container">
|
<div class="app-card-container">
|
||||||
<v-card
|
<v-card
|
||||||
class="mx-auto h-100 d-flex flex-column app-card-disabled"
|
class="mx-auto h-100 d-flex flex-column"
|
||||||
|
:class="{ 'app-card-disabled': !isIosAppEnabled }"
|
||||||
elevation="4"
|
elevation="4"
|
||||||
:class="{ 'card-disabled': true }"
|
:hover="isIosAppEnabled"
|
||||||
|
:to="isIosAppEnabled ? '/app' : undefined"
|
||||||
>
|
>
|
||||||
<v-card-item class="bg-green-darken-3">
|
<v-card-item class="bg-green-darken-3">
|
||||||
<v-card-title class="text-h5 font-weight-bold text-white">
|
<v-card-title class="text-h5 font-weight-bold text-white">
|
||||||
@@ -93,16 +95,17 @@
|
|||||||
color="green-darken-2"
|
color="green-darken-2"
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
size="large"
|
size="large"
|
||||||
disabled
|
:disabled="!isIosAppEnabled"
|
||||||
|
:to="isIosAppEnabled ? '/app' : undefined"
|
||||||
>
|
>
|
||||||
Download App
|
{{ isIosAppEnabled ? 'Download App' : 'Download App' }}
|
||||||
<v-icon icon="mdi-arrow-right" end></v-icon>
|
<v-icon icon="mdi-arrow-right" end></v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<!-- Coming Soon Banner -->
|
<!-- Coming Soon Banner -->
|
||||||
<div class="coming-soon-banner">
|
<div v-if="shouldShowComingSoon" class="coming-soon-banner">
|
||||||
<v-chip
|
<v-chip
|
||||||
color="warning"
|
color="warning"
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
@@ -121,6 +124,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ALPRVerificationDialog from '@/components/ALPRVerificationDialog.vue';
|
import ALPRVerificationDialog from '@/components/ALPRVerificationDialog.vue';
|
||||||
|
import { useFeatureFlags } from '@/composables/useFeatureFlags';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const { flags } = useFeatureFlags();
|
||||||
|
|
||||||
|
// Computed properties for iOS app state
|
||||||
|
const isIosAppEnabled = computed(() => flags.value?.iosApp.enabled ?? false);
|
||||||
|
const shouldShowComingSoon = computed(() => !isIosAppEnabled.value);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -138,7 +149,7 @@ import ALPRVerificationDialog from '@/components/ALPRVerificationDialog.vue';
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-disabled {
|
.app-card-disabled {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
Reference in new issue
Block a user