Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ Follow @CONTRIBUTING.md for commit, branch, code style, and workflow conventions
- `opass.json.get.ts` — OPass integration

**Icons:** Custom icon collection in `app/assets/icons/`, referenced as `local:icon-name`.

**Venue floor plans:** `app/assets/venue/raw/` holds the designer's Affinity Designer exports; `pnpm prepare:venue-svg` optimizes them into `app/assets/venue/` and regenerates `fonts.css`. Both directories are committed — do not edit the generated files by hand. `app/pages/venue.vue` inlines the plans with `v-html` rather than `<img>`, because an `<img>`-hosted SVG cannot reach the page's fonts and the exports hard-code a per-glyph `x` offset per character. `RB-AU` stays a webp: its SVG export contains no vector data. See the header comment in `scripts/prepare-venue-svg.mjs`.
1 change: 1 addition & 0 deletions app/assets/venue/TR2F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed app/assets/venue/TR2F.webp
Binary file not shown.
550 changes: 1 addition & 549 deletions app/assets/venue/TR309.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
518 changes: 1 addition & 517 deletions app/assets/venue/TR312.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/venue/TR3F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed app/assets/venue/TR3F.webp
Binary file not shown.
1 change: 1 addition & 0 deletions app/assets/venue/TR409-1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed app/assets/venue/TR409-1.webp
Binary file not shown.
1 change: 1 addition & 0 deletions app/assets/venue/TR4F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed app/assets/venue/TR4F.webp
Binary file not shown.
1 change: 1 addition & 0 deletions app/assets/venue/TR5F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed app/assets/venue/TR5F.webp
Binary file not shown.
28 changes: 28 additions & 0 deletions app/assets/venue/fonts.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/assets/venue/overview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed app/assets/venue/overview.webp
Binary file not shown.
1,198 changes: 1,198 additions & 0 deletions app/assets/venue/raw/TR2F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
549 changes: 549 additions & 0 deletions app/assets/venue/raw/TR309.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
517 changes: 517 additions & 0 deletions app/assets/venue/raw/TR312.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
893 changes: 893 additions & 0 deletions app/assets/venue/raw/TR3F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,415 changes: 1,415 additions & 0 deletions app/assets/venue/raw/TR409-1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,892 changes: 1,892 additions & 0 deletions app/assets/venue/raw/TR4F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,458 changes: 1,458 additions & 0 deletions app/assets/venue/raw/TR5F.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,377 changes: 1,377 additions & 0 deletions app/assets/venue/raw/overview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
145 changes: 106 additions & 39 deletions app/pages/venue.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import overview from '~/assets/venue/overview.webp'
// The overview tab is what renders first, so its markup ships with the page
// instead of costing an extra round trip.
import overviewSvg from '~/assets/venue/overview.svg?raw'
import rbau from '~/assets/venue/RB-AU.webp'
import tr2f from '~/assets/venue/TR2F.webp'
import tr3f from '~/assets/venue/TR3F.webp'
import tr4f from '~/assets/venue/TR4F.webp'
import tr5f from '~/assets/venue/TR5F.webp'
import tr309 from '~/assets/venue/TR309.svg'
import tr312 from '~/assets/venue/TR312.svg'
import tr409_1 from '~/assets/venue/TR409-1.webp'
import '~/assets/venue/fonts.css'

const { t } = useI18n()

Expand All @@ -21,36 +17,78 @@ useSeoMeta({

type Category = 'floors' | 'booths' | 'overview'

interface Plan {
/** Matches the file name in `~/assets/venue/`. */
key: string
alt: string
/** Set only for plans that have no usable vector source and stay raster. */
src?: string
}

// Every plan shares this viewBox, so one ratio keeps the layout from shifting
// while a plan's chunk is still loading.
const ASPECT_RATIO = '7083 / 4753'

const categories: Category[] = ['overview', 'floors', 'booths']
const activeCategory = ref<Category>('overview')

const overviewImage = [
{ key: 'overview', alt: 'Overview', src: overview },
]

const floorImages = [
{ key: 'RBAU', alt: 'RB/AU', src: rbau },
{ key: 'TR2F', alt: 'TR 2F', src: tr2f },
{ key: 'TR3F', alt: 'TR 3F', src: tr3f },
{ key: 'TR4F', alt: 'TR 4F', src: tr4f },
{ key: 'TR5F', alt: 'TR 5F', src: tr5f },
]

const boothImages = [
{ key: 'TR309', alt: 'TR309', src: tr309 },
{ key: 'TR312', alt: 'TR312', src: tr312 },
{ key: 'TR409-1', alt: 'TR409-1', src: tr409_1 },
]

const activeImages = computed(() => {
if (activeCategory.value === 'floors') {
return floorImages
} else if (activeCategory.value === 'booths') {
return boothImages
} else {
return overviewImage
}
const plans: Record<Category, Plan[]> = {
overview: [
{ key: 'overview', alt: 'Overview' },
],
floors: [
// RB-AU's SVG export holds no vector data at all — it is one embedded PNG in
// an <svg> wrapper, 1.1 MB against 235 KB for the webp — so this plan alone
// stays a raster image. See scripts/prepare-venue-svg.mjs.
{ key: 'RB-AU', alt: 'RB/AU', src: rbau },
{ key: 'TR2F', alt: 'TR 2F' },
{ key: 'TR3F', alt: 'TR 3F' },
{ key: 'TR4F', alt: 'TR 4F' },
{ key: 'TR5F', alt: 'TR 5F' },
],
booths: [
{ key: 'TR309', alt: 'TR309' },
{ key: 'TR312', alt: 'TR312' },
{ key: 'TR409-1', alt: 'TR409-1' },
],
}

const activePlans = computed(() => plans[activeCategory.value])

// The plans are inlined rather than loaded through <img> because an <img>-hosted
// SVG is an isolated document that cannot reach the page's fonts, and these
// exports hard-code a per-glyph x offset for every character. Inlining lets
// fonts.css apply and the labels keep their intended spacing.
// overview is excluded because it is imported statically above; letting it match
// here too would make Vite warn that the dynamic import cannot split it out.
const svgLoaders = import.meta.glob<string>([
'../assets/venue/*.svg',
'!../assets/venue/overview.svg',
], {
query: '?raw',
import: 'default',
})

const svgMarkup = reactive<Record<string, string>>({ overview: overviewSvg })

async function loadPlan(key: string) {
if (svgMarkup[key]) {
return
}

const loader = svgLoaders[`../assets/venue/${key}.svg`]
if (loader) {
svgMarkup[key] = await loader()
}
}

watch(activeCategory, (category) => {
for (const plan of plans[category]) {
if (!plan.src) {
loadPlan(plan.key)
}
}
}, { immediate: true })
</script>

<template>
Expand All @@ -72,16 +110,45 @@ const activeImages = computed(() => {
</div>

<div class="flex flex-col gap-4">
<img
v-for="img in activeImages"
:key="img.key"
:alt="img.alt"
:src="img.src"
<template
v-for="plan in activePlans"
:key="plan.key"
>
<img
v-if="plan.src"
:alt="plan.alt"
class="h-auto w-full"
height="4753"
:src="plan.src"
width="7084"
>
<!-- eslint-disable-next-line vue/no-v-html -- build output of scripts/prepare-venue-svg.mjs, no user input involved -->
<div
v-else-if="svgMarkup[plan.key]"
:aria-label="plan.alt"
class="venue-plan w-full"
role="img"
:style="{ aspectRatio: ASPECT_RATIO }"
v-html="svgMarkup[plan.key]"
/>
<div
v-else
class="rounded bg-gray-100 w-full animate-pulse"
:style="{ aspectRatio: ASPECT_RATIO }"
/>
</template>
</div>
</div>
</template>

<style scoped>
.venue-plan :deep(svg) {
display: block;
width: 100%;
height: 100%;
}
</style>

<i18n lang="yaml">
zh:
title: 會場地圖
Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default withNuxt(
{
ignores: [
'.pnpm-store/', // Generated by CI workflow
'app/assets/venue/fonts.css', // Generated by scripts/prepare-venue-svg.mjs
],
},
)
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dev": "nuxt dev",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"prepare:venue-svg": "node scripts/prepare-venue-svg.mjs",
"lint": "eslint",
"typecheck": "nuxt typecheck"
},
Expand Down Expand Up @@ -50,6 +51,7 @@
"cross-env": "10.1.0",
"eslint-plugin-format": "2.0.1",
"nuxt-og-image": "6.5.2",
"svgo": "4.0.2",
"typescript": "6.0.3",
"unocss": "66.7.3",
"unocss-preset-theme": "0.14.1",
Expand Down
13 changes: 8 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading