diff --git a/webapp/src/App.vue b/webapp/src/App.vue
index 0b35159..eb139fb 100644
--- a/webapp/src/App.vue
+++ b/webapp/src/App.vue
@@ -3,12 +3,13 @@ import { RouterView, useRouter } from 'vue-router'
import { computed, ref, watch, onMounted } from 'vue'
import { useTheme } from 'vuetify';
import DiscordWarningDialog from '@/components/DiscordWarningDialog.vue';
+import OfficialSocials from '@/components/layout/OfficialSocials.vue';
import { useDiscordIntercept } from '@/composables/useDiscordIntercept';
const theme = useTheme();
const router = useRouter();
-const isDark = computed(() => theme.name.value === 'dark');
const isInIframe = computed(() => window.self !== window.top);
+
const { showDialog, discordUrl, interceptDiscordLinks } = useDiscordIntercept();
function toggleTheme() {
@@ -37,24 +38,27 @@ const items = [
{ title: 'Home', icon: 'mdi-home', to: '/' },
{ title: 'Map', icon: 'mdi-map', href: 'https://maps.deflock.org' },
{ title: 'Learn', icon: 'mdi-school', to: '/what-is-an-alpr' },
- { title: 'Store', icon: 'mdi-shopping', to: '/store' },
+ { title: 'Donate', icon: 'mdi-cash-multiple', to: '/donate' },
+
]
-const contributeItems = [
- { title: 'Submit Cameras', icon: 'mdi-map-marker-plus', to: '/report' },
+const resourceItems = [
+ { title: 'Store', icon: 'mdi-shopping', to: '/store', isNew: true },
+ { title: 'FAQ', icon: 'mdi-help-circle', to: '/what-is-an-alpr' },
{ title: 'Public Records', icon: 'mdi-file-document', to: '/foia' },
{ title: 'City Council', icon: 'mdi-account-voice', to: '/council' },
- { title: 'Support Us', icon: 'mdi-cash-multiple', to: '/donate' },
]
-const metaItems = [
- { title: 'Discord', customIcon: '/icon-discord.svg', customIconDark: '/icon-discord-white.svg', customIconGrey: '/icon-discord-grey.svg', href: 'https://discord.gg/aV7v4R3sKT'},
+const getInvolvedItems = [
+ { title: 'Submit Cameras', icon: 'mdi-map-marker-plus', to: '/report' },
{ title: 'Local Groups', icon: 'mdi-account-group', to: '/groups' },
{ title: 'Contact', icon: 'mdi-email-outline', to: '/contact' },
{ title: 'GitHub', icon: 'mdi-github', href: 'https://github.com/frillweeman/deflock'},
];
+
const drawer = ref(false)
+
watch(() => theme.global.name.value, (newTheme) => {
const root = document.documentElement;
if (newTheme === 'dark') {
@@ -105,12 +109,22 @@ watch(() => theme.global.name.value, (newTheme) => {
:prepend-icon="item.icon"
>
{{ item.title }}
+
© {{ currentYear }} DeFlock. All Rights Reserved
@@ -78,14 +108,17 @@ const internalLinks = [ { title: 'About', to: '/about', icon: 'mdi-information', alt: 'About' }, { title: 'Privacy Policy', to: '/privacy', icon: 'mdi-shield-lock', alt: 'Privacy Policy' }, { title: 'Terms of Service', to: '/terms', icon: 'mdi-file-document', alt: 'Terms of Service' }, - { title: 'Press', to: '/press', icon: 'mdi-newspaper', alt: 'Press' }, ]; -const externalLinks = [ - { title: 'Discord', href: 'https://discord.gg/aV7v4R3sKT', customIcon: '/icon-discord.svg', customIconDark: '/icon-discord-white.svg', alt: 'Discord Logo' }, - { title: 'Support Us', to: '/donate', icon: 'mdi-cash-multiple', alt: 'Support Us' }, - { title: 'GitHub', href: 'https://github.com/FoggedLens/deflock', icon: 'mdi-github', alt: 'GitHub Logo' }, +const contactLinks = [ + { title: 'Press', to: '/press', icon: 'mdi-newspaper', alt: 'Press' }, { title: 'Contact', to: '/contact', icon: 'mdi-email', alt: 'Contact' }, +]; + +const getInvolvedLinks = [ + { title: 'Local Groups', to: '/groups', icon: 'mdi-account-group' }, + { title: 'GitHub', href: 'https://github.com/FoggedLens/deflock', icon: 'mdi-github' }, + { title: 'Submit Cameras', to: '/report', icon: 'mdi-map-marker-plus' }, ] @@ -97,4 +130,46 @@ const externalLinks = [ font-size: 0.85rem; line-height: 0.5rem; } + +/* Plain flexbox + wrap, rather than CSS grid's auto-fit/minmax: grid's + auto-fit track-counting algorithm doesn't properly support intrinsic + sizes (max-content) as a track minimum, which is why that approach + either clipped text, wrapped it mid-word, or collapsed to one column + depending on the minimum used. Flexbox with flex-wrap naturally sizes + each item to its content and only wraps an item to the next line once + it genuinely doesn't fit — no guessed pixel thresholds required. + + Columns are NOT stretched (flex-grow: 0) — each stays sized to its own + content. Letting them grow to fill space (a prior revision) made wider + columns pad out with empty space to the right of their left-aligned + text, which visually dragged the whole row's "center of mass" left of + the row's true center — an effect that got worse the more room there + was to stretch into, i.e. on wider screens. + + Instead, `justify-content: space-evenly` distributes the *gaps* — both + between columns and on the outer edges — based on the container's + actual width. That gap recalculates continuously as the viewport + resizes, so spacing scales smoothly with no fixed max-width/threshold + needed and no snapping between layout states. */ +.footer-links-grid { + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + row-gap: 24px; +} + +.footer-links-grid > div { + flex: 0 0 auto; +} +/* Keep each link label on a single line so a column reflows to its own + row instead of wrapping mid-word when space is tight. */ +.footer-links-grid :deep(.v-list-item-title) { + white-space: nowrap; +} + + + + + + diff --git a/webapp/src/components/layout/OfficialSocials.vue b/webapp/src/components/layout/OfficialSocials.vue new file mode 100644 index 0000000..0eb4e9a --- /dev/null +++ b/webapp/src/components/layout/OfficialSocials.vue @@ -0,0 +1,87 @@ + +{{ collectionDescription }}
+