@citizenplane/pimp 18.23.1 → 18.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/components/CpAncillaryItem.vue.d.ts +5 -0
  2. package/dist/components/CpAncillaryItem.vue.d.ts.map +1 -1
  3. package/dist/components/CpQuantityCounter.vue.d.ts +1 -0
  4. package/dist/components/CpQuantityCounter.vue.d.ts.map +1 -1
  5. package/dist/pimp.es.js +1733 -1704
  6. package/dist/pimp.umd.js +40 -40
  7. package/dist/style.css +1 -1
  8. package/package.json +1 -1
  9. package/src/assets/images/ancillaries/airport-transfer.png +0 -0
  10. package/src/assets/images/ancillaries/bicycle.png +0 -0
  11. package/src/assets/images/ancillaries/cabin-bag.png +0 -0
  12. package/src/assets/images/ancillaries/checked-bag-size-1.png +0 -0
  13. package/src/assets/images/ancillaries/checked-bag-size-2.png +0 -0
  14. package/src/assets/images/ancillaries/checked-bag-size-3.png +0 -0
  15. package/src/assets/images/ancillaries/checked-bag.png +0 -0
  16. package/src/assets/images/ancillaries/connectivity.png +0 -0
  17. package/src/assets/images/ancillaries/diving-set.png +0 -0
  18. package/src/assets/images/ancillaries/extra-weight.png +0 -0
  19. package/src/assets/images/ancillaries/fast-track.png +0 -0
  20. package/src/assets/images/ancillaries/firearm.png +0 -0
  21. package/src/assets/images/ancillaries/fishing-rod.png +0 -0
  22. package/src/assets/images/ancillaries/golf.png +0 -0
  23. package/src/assets/images/ancillaries/hand-bag.png +0 -0
  24. package/src/assets/images/ancillaries/kite-surf.png +0 -0
  25. package/src/assets/images/ancillaries/lounge-access.png +0 -0
  26. package/src/assets/images/ancillaries/meet-and-greet.png +0 -0
  27. package/src/assets/images/ancillaries/music.png +0 -0
  28. package/src/assets/images/ancillaries/oversized.png +0 -0
  29. package/src/assets/images/ancillaries/pet-in-cabin.png +0 -0
  30. package/src/assets/images/ancillaries/pet-in-hold.png +0 -0
  31. package/src/assets/images/ancillaries/skis.png +0 -0
  32. package/src/assets/images/ancillaries/special-meal.png +0 -0
  33. package/src/assets/images/ancillaries/standard-meal.png +0 -0
  34. package/src/components/CpAncillaryItem.vue +167 -7
  35. package/src/components/CpQuantityCounter.vue +3 -2
  36. package/src/stories/CpAncillaryItem.stories.ts +216 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "18.23.1",
3
+ "version": "18.23.3",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -3,16 +3,32 @@
3
3
  <div class="cpAncillaryItem__wrap">
4
4
  <div class="cpAncillaryItem__visual">
5
5
  <img v-if="ancillaryImage" alt="" class="cpAncillaryItem__image" :src="ancillaryImage" />
6
- <cp-icon v-else class="cpAncillaryItem__fallbackIcon" size="40" type="shopping-bag" />
6
+ <cp-icon v-else class="cpAncillaryItem__fallbackIcon" :size="fallbackIconSize" type="shopping-bag" />
7
7
  </div>
8
8
  <div class="cpAncillaryItem__content">
9
9
  <div class="cpAncillaryItem__name">
10
- <p class="cpAncillaryItem__title">{{ title }}</p>
10
+ <div class="cpAncillaryItem__heading">
11
+ <p class="cpAncillaryItem__title">{{ title }}</p>
12
+ <p v-if="hasInlinePrice" class="cpAncillaryItem__bullet">•</p>
13
+ <p v-if="hasInlinePrice" class="cpAncillaryItem__inlinePrice">{{ formattedPrice }}</p>
14
+ </div>
11
15
  <p class="cpAncillaryItem__description">{{ description }}</p>
12
16
  <p v-if="subtitle" class="cpAncillaryItem__subtitle">{{ subtitle }}</p>
13
17
  </div>
14
18
  <div class="cpAncillaryItem__quantity">
19
+ <cp-button
20
+ v-if="isAddButtonVisible"
21
+ appearance="secondary"
22
+ class="cpAncillaryItem__addButton"
23
+ color="accent"
24
+ :disabled="disabled"
25
+ size="sm"
26
+ @click="selectFirstQuantity"
27
+ >
28
+ {{ addLabel }}
29
+ </cp-button>
15
30
  <cp-quantity-counter
31
+ v-else
16
32
  v-model="quantity"
17
33
  class="cpAncillaryItem__counter"
18
34
  :decrease-label="decreaseLabel"
@@ -20,9 +36,9 @@
20
36
  :increase-label="increaseLabel"
21
37
  :max="maxQuantity"
22
38
  />
23
- <span v-if="hasMaxQuantity" class="cpAncillaryItem__maxQuantity">{{ maxQuantityLabel }}</span>
39
+ <span v-if="hasMaxQuantityCaption" class="cpAncillaryItem__maxQuantity">{{ maxQuantityLabel }}</span>
24
40
  </div>
25
- <p v-if="formattedPrice" class="cpAncillaryItem__price">{{ formattedPrice }}</p>
41
+ <p v-if="hasStackedPrice" class="cpAncillaryItem__price">{{ formattedPrice }}</p>
26
42
  </div>
27
43
  </div>
28
44
  </div>
@@ -31,6 +47,7 @@
31
47
  <script setup lang="ts">
32
48
  import { computed } from 'vue'
33
49
 
50
+ import CpButton from '@/components/CpButton.vue'
34
51
  import CpIcon from '@/components/CpIcon.vue'
35
52
  import CpQuantityCounter from '@/components/CpQuantityCounter.vue'
36
53
 
@@ -62,9 +79,12 @@ import StandardMealImage from '@/assets/images/ancillaries/standard-meal.png'
62
79
  import { CpAncillaryItemTypes } from '@/constants/CpAncillaryItemTypes'
63
80
 
64
81
  interface Props {
82
+ addLabel?: string
65
83
  currency?: string
66
84
  description: string
67
85
  disableAnimation?: boolean
86
+ disabled?: boolean
87
+ layout?: 'card' | 'list'
68
88
  locale?: string
69
89
  maxQuantity?: number | null
70
90
  maxQuantityLabel?: string
@@ -76,6 +96,8 @@ interface Props {
76
96
  }
77
97
 
78
98
  const props = withDefaults(defineProps<Props>(), {
99
+ addLabel: 'Add',
100
+ layout: 'card',
79
101
  locale: () => (typeof navigator === 'undefined' ? 'en' : navigator.language),
80
102
  maxQuantity: null,
81
103
  vertical: false,
@@ -116,16 +138,40 @@ const ancillaryImages: Partial<Record<CpAncillaryItemTypes, string>> = {
116
138
 
117
139
  const ancillaryImage = computed(() => ancillaryImages[props.type] ?? null)
118
140
 
141
+ const isListLayout = computed(() => props.layout === 'list')
142
+
143
+ const fallbackIconSize = computed(() => {
144
+ if (isListLayout.value) return '28'
145
+ return '40'
146
+ })
147
+
119
148
  const hasMaxQuantity = computed(() => props.maxQuantity !== null)
120
149
 
150
+ const hasMaxQuantityCaption = computed(() => hasMaxQuantity.value && !isListLayout.value)
151
+
152
+ const isAddButtonVisible = computed(() => quantity.value < 1)
153
+
154
+ const selectFirstQuantity = (): void => {
155
+ quantity.value = 1
156
+ }
157
+
121
158
  const formattedPrice = computed(() => {
122
159
  if (props.price === undefined || !props.currency) return null
123
160
  return new Intl.NumberFormat(props.locale, { style: 'currency', currency: props.currency }).format(props.price / 100)
124
161
  })
125
162
 
163
+ const hasPrice = computed(() => formattedPrice.value !== null)
164
+
165
+ const hasInlinePrice = computed(() => hasPrice.value && isListLayout.value)
166
+
167
+ const hasStackedPrice = computed(() => hasPrice.value && !isListLayout.value)
168
+
169
+ const isVerticalCard = computed(() => props.vertical && !isListLayout.value)
170
+
126
171
  const isSelected = computed(() => quantity.value > 0)
127
172
  const cpAncillaryItemDynamicClass = computed(() => ({
128
- 'cpAncillaryItem--isVertical': props.vertical,
173
+ 'cpAncillaryItem--isList': isListLayout.value,
174
+ 'cpAncillaryItem--isVertical': isVerticalCard.value,
129
175
  'cpAncillaryItem--isSelected': isSelected.value,
130
176
  }))
131
177
  </script>
@@ -256,7 +302,7 @@ const cpAncillaryItemDynamicClass = computed(() => ({
256
302
  }
257
303
  }
258
304
 
259
- &--isSelected {
305
+ &--isSelected:not(#{&}--isList) {
260
306
  border-color: var(--cp-border-accent-solid);
261
307
  outline: fn.px-to-rem(1) solid var(--cp-border-accent-solid);
262
308
  outline-offset: fn.px-to-rem(-2);
@@ -270,8 +316,122 @@ const cpAncillaryItemDynamicClass = computed(() => ({
270
316
  }
271
317
  }
272
318
 
319
+ &--isList {
320
+ padding: 0;
321
+ border: none;
322
+ border-radius: 0;
323
+ background: none;
324
+ container-type: inline-size;
325
+
326
+ .cpAncillaryItem__wrap {
327
+ align-items: center;
328
+ padding: var(--cp-spacing-lg) 0;
329
+ border-radius: 0;
330
+ border-bottom: fn.px-to-rem(1) solid var(--cp-border-soft);
331
+ }
332
+
333
+ .cpAncillaryItem__visual {
334
+ width: fn.px-to-rem(44);
335
+ height: fn.px-to-rem(44);
336
+ border-radius: 0;
337
+ background: none;
338
+ }
339
+
340
+ .cpAncillaryItem__image {
341
+ width: fn.px-to-rem(28);
342
+ height: fn.px-to-rem(28);
343
+ }
344
+
345
+ .cpAncillaryItem__content {
346
+ flex-direction: row;
347
+ align-items: center;
348
+ padding: 0;
349
+ }
350
+
351
+ .cpAncillaryItem__name {
352
+ flex: auto;
353
+ }
354
+
355
+ .cpAncillaryItem__heading {
356
+ display: flex;
357
+ flex-direction: column;
358
+ }
359
+
360
+ .cpAncillaryItem__title {
361
+ font-weight: 500;
362
+ }
363
+
364
+ .cpAncillaryItem__bullet,
365
+ .cpAncillaryItem__inlinePrice {
366
+ color: var(--cp-text-secondary);
367
+ font-size: var(--cp-text-size-md);
368
+ font-weight: 500;
369
+ line-height: var(--cp-text-md-line-height);
370
+ }
371
+
372
+ .cpAncillaryItem__bullet {
373
+ display: none;
374
+ }
375
+
376
+ .cpAncillaryItem__quantity {
377
+ flex: none;
378
+ }
379
+
380
+ &:last-child .cpAncillaryItem__wrap {
381
+ border-bottom: none;
382
+ }
383
+
384
+ @include mx.media-query-min(48rem) {
385
+ .cpAncillaryItem__heading {
386
+ flex-direction: row;
387
+ gap: var(--cp-spacing-sm);
388
+ }
389
+
390
+ .cpAncillaryItem__bullet {
391
+ display: block;
392
+ }
393
+ }
394
+
395
+ @container (width < 22rem) {
396
+ .cpAncillaryItem__wrap {
397
+ gap: var(--cp-spacing-sm);
398
+ }
399
+
400
+ .cpAncillaryItem__visual {
401
+ width: fn.px-to-rem(40);
402
+ height: fn.px-to-rem(40);
403
+ }
404
+
405
+ .cpAncillaryItem__image {
406
+ width: fn.px-to-rem(24);
407
+ height: fn.px-to-rem(24);
408
+ }
409
+
410
+ .cpAncillaryItem__content {
411
+ flex-wrap: wrap;
412
+ align-items: center;
413
+ gap: var(--cp-spacing-md);
414
+ }
415
+
416
+ .cpAncillaryItem__heading {
417
+ flex-direction: column;
418
+ gap: 0;
419
+ }
420
+
421
+ .cpAncillaryItem__bullet {
422
+ display: none;
423
+ }
424
+
425
+ .cpAncillaryItem__description,
426
+ .cpAncillaryItem__subtitle {
427
+ font-size: var(--cp-text-size-xs);
428
+ line-height: var(--cp-text-xs-line-height);
429
+ }
430
+ }
431
+ }
432
+
273
433
  @media (hover: hover) {
274
- &:hover .cpAncillaryItem__image {
434
+ &:not(#{&}--isList):hover .cpAncillaryItem__image {
275
435
  transform: scale3d(1.1, 1.1, 1);
276
436
  }
277
437
  }
@@ -34,6 +34,7 @@ import CpTransitionCounter from '@/components/CpTransitionCounter.vue'
34
34
  interface Props {
35
35
  decreaseLabel: string
36
36
  disableAnimation?: boolean
37
+ disabled?: boolean
37
38
  increaseLabel: string
38
39
  max?: number | null
39
40
  min?: number
@@ -43,8 +44,8 @@ const props = withDefaults(defineProps<Props>(), { disableAnimation: false, max:
43
44
 
44
45
  const modelValue = defineModel<number>({ required: true })
45
46
 
46
- const isDecrementDisabled = computed(() => modelValue.value <= props.min)
47
- const isIncrementDisabled = computed(() => props.max !== null && modelValue.value >= props.max)
47
+ const isDecrementDisabled = computed(() => props.disabled || modelValue.value <= props.min)
48
+ const isIncrementDisabled = computed(() => props.disabled || (props.max !== null && modelValue.value >= props.max))
48
49
 
49
50
  const decrement = () => {
50
51
  if (isDecrementDisabled.value) return
@@ -8,10 +8,43 @@ import { docCellStyle, docLabelStyle, docRowWrapStyle } from '@/stories/document
8
8
 
9
9
  const galleryCellStyle = `${docCellStyle} width: 260px;`
10
10
 
11
+ const listStackRows = [
12
+ {
13
+ title: 'Hand bag',
14
+ description: '5kg • 45 × 36 × 20 cm',
15
+ type: CpAncillaryItemTypes.HAND_BAG,
16
+ price: 990,
17
+ quantity: 0,
18
+ },
19
+ {
20
+ title: 'Cabin bag',
21
+ description: '10kg • 55 × 36 × 25 cm',
22
+ type: CpAncillaryItemTypes.CABIN_BAG,
23
+ price: 1990,
24
+ quantity: 1,
25
+ },
26
+ {
27
+ title: 'Checked bag',
28
+ description: '23kg • 158 cm total',
29
+ type: CpAncillaryItemTypes.CHECKED_BAG,
30
+ price: 2990,
31
+ quantity: 0,
32
+ },
33
+ ]
34
+
35
+ const listCompactRows = listStackRows.slice(0, 2)
36
+
37
+ const listWrapStyle = 'padding: 20px 0; width: 358px;'
38
+ const listCompactWrapStyle = 'padding: 20px 0; width: 21rem;'
39
+
11
40
  const meta = {
12
41
  title: 'Business/CpAncillaryItem',
13
42
  component: CpAncillaryItem,
14
43
  argTypes: {
44
+ addLabel: {
45
+ control: 'text',
46
+ description: 'Label of the button shown while the quantity is 0',
47
+ },
15
48
  currency: {
16
49
  control: 'text',
17
50
  description: 'ISO currency code used to format price',
@@ -20,6 +53,15 @@ const meta = {
20
53
  control: 'text',
21
54
  description: 'Description text for the ancillary',
22
55
  },
56
+ disabled: {
57
+ control: 'boolean',
58
+ description: 'Whether the ancillary is disabled',
59
+ },
60
+ layout: {
61
+ control: 'select',
62
+ options: ['card', 'list'],
63
+ description: 'Card renders the bordered tile, list renders a separated row',
64
+ },
23
65
  locale: {
24
66
  control: 'text',
25
67
  description: 'Locale used to format price, defaults to the browser locale',
@@ -60,7 +102,8 @@ export default meta
60
102
  type Story = StoryObj<typeof meta>
61
103
 
62
104
  /**
63
- * Default ancillary card. Use the controls to experiment with each prop in isolation.
105
+ * Default ancillary card. It starts on the Add button, which sets the quantity to 1
106
+ * and hands over to the counter. Use the controls to experiment with each prop in isolation.
64
107
  */
65
108
  export const Default: Story = {
66
109
  args: {
@@ -93,7 +136,8 @@ export const Default: Story = {
93
136
  /* -------------------------------------------------------------------------- */
94
137
 
95
138
  /**
96
- * quantity > 0 adds the accent ring and swaps the counter background.
139
+ * quantity > 0 replaces the Add button with the counter, adds the accent ring and
140
+ * swaps the counter background.
97
141
  */
98
142
  export const Selected: Story = {
99
143
  args: {
@@ -115,7 +159,7 @@ export const Selected: Story = {
115
159
  }
116
160
 
117
161
  /**
118
- * No maxQuantity: the counter has no upper bound and the caption is hidden.
162
+ * No maxQuantity: once added, the counter has no upper bound and the caption is hidden.
119
163
  */
120
164
  export const Uncapped: Story = {
121
165
  args: {
@@ -227,6 +271,159 @@ export const VerticalOnMobile: Story = {
227
271
  }),
228
272
  }
229
273
 
274
+ /* -------------------------------------------------------------------------- */
275
+ /* List layout */
276
+ /* -------------------------------------------------------------------------- */
277
+
278
+ /**
279
+ * The list layout: no card frame, a 44px visual, title and price on one line and
280
+ * a soft bottom separator so stacked rows read as a list. maxQuantityLabel is not
281
+ * rendered here.
282
+ */
283
+ export const List: Story = {
284
+ args: {
285
+ title: 'Hand bag',
286
+ description: '5kg • 45 × 36 × 20 cm',
287
+ type: CpAncillaryItemTypes.HAND_BAG,
288
+ quantity: 0,
289
+ maxQuantity: 10,
290
+ maxQuantityLabel: 'max. 10',
291
+ price: 990,
292
+ currency: 'EUR',
293
+ layout: 'list',
294
+ },
295
+ render: (args) => ({
296
+ components: { CpAncillaryItem },
297
+ setup() {
298
+ const quantity = ref(0)
299
+ return { args, quantity, listWrapStyle }
300
+ },
301
+ template: `
302
+ <div :style="listWrapStyle">
303
+ <CpAncillaryItem v-bind="args" v-model:quantity="quantity" />
304
+ </div>
305
+ `,
306
+ }),
307
+ }
308
+
309
+ /**
310
+ * Selected list row: the counter replaces the Add button and the row keeps its
311
+ * plain background, without the accent ring the card gets.
312
+ */
313
+ export const ListSelected: Story = {
314
+ args: {
315
+ ...List.args,
316
+ quantity: 1,
317
+ },
318
+ render: (args) => ({
319
+ components: { CpAncillaryItem },
320
+ setup() {
321
+ const quantity = ref(1)
322
+ return { args, quantity, listWrapStyle }
323
+ },
324
+ template: `
325
+ <div :style="listWrapStyle">
326
+ <CpAncillaryItem v-bind="args" v-model:quantity="quantity" />
327
+ </div>
328
+ `,
329
+ }),
330
+ }
331
+
332
+ /**
333
+ * Below the tablet breakpoint the title, the price and the description stack on
334
+ * three lines and the bullet disappears.
335
+ */
336
+ export const ListOnMobile: Story = {
337
+ args: {
338
+ ...List.args,
339
+ },
340
+ globals: {
341
+ viewport: { value: '390-844', isRotated: false },
342
+ },
343
+ render: (args) => ({
344
+ components: { CpAncillaryItem },
345
+ setup() {
346
+ const quantity = ref(0)
347
+ return { args, quantity, listWrapStyle }
348
+ },
349
+ template: `
350
+ <div :style="listWrapStyle">
351
+ <CpAncillaryItem v-bind="args" v-model:quantity="quantity" />
352
+ </div>
353
+ `,
354
+ }),
355
+ }
356
+
357
+ /**
358
+ * Under 22rem of component width the row goes compact: a 4px gap to a 40px visual
359
+ * holding a 24px image, the details wrap with an 8px gap, and the description drops
360
+ * to 12px. Driven by the component's own width, so a 336px side panel switches while
361
+ * the wider list does not.
362
+ */
363
+ export const ListCompact: Story = {
364
+ args: {
365
+ ...List.args,
366
+ },
367
+ parameters: { controls: { disable: true } },
368
+ render: () => ({
369
+ components: { CpAncillaryItem },
370
+ setup() {
371
+ const rows = listCompactRows
372
+ const quantities = ref(Object.fromEntries(rows.map((row) => [row.type, row.quantity])))
373
+ return { rows, quantities, listCompactWrapStyle }
374
+ },
375
+ template: `
376
+ <div :style="listCompactWrapStyle">
377
+ <CpAncillaryItem
378
+ v-for="row in rows"
379
+ :key="row.type"
380
+ v-model:quantity="quantities[row.type]"
381
+ :title="row.title"
382
+ :description="row.description"
383
+ :type="row.type"
384
+ :price="row.price"
385
+ currency="EUR"
386
+ layout="list"
387
+ />
388
+ </div>
389
+ `,
390
+ }),
391
+ }
392
+
393
+ /**
394
+ * Several rows in a row, to show how the separators build a list. The last row drops
395
+ * its separator so the list does not end on a rule.
396
+ */
397
+ export const ListStack: Story = {
398
+ args: {
399
+ ...List.args,
400
+ },
401
+ parameters: { controls: { disable: true } },
402
+ render: () => ({
403
+ components: { CpAncillaryItem },
404
+ setup() {
405
+ const rows = listStackRows
406
+ const quantities = ref(Object.fromEntries(rows.map((row) => [row.type, row.quantity])))
407
+ return { rows, quantities, listWrapStyle }
408
+ },
409
+ template: `
410
+ <div :style="listWrapStyle">
411
+ <CpAncillaryItem
412
+ v-for="row in rows"
413
+ :key="row.type"
414
+ v-model:quantity="quantities[row.type]"
415
+ :title="row.title"
416
+ :description="row.description"
417
+ :type="row.type"
418
+ :price="row.price"
419
+ currency="EUR"
420
+ layout="list"
421
+ />
422
+ </div>
423
+ `,
424
+ }),
425
+ }
426
+
230
427
  /* -------------------------------------------------------------------------- */
231
428
  /* Gallery */
232
429
  /* -------------------------------------------------------------------------- */
@@ -244,15 +441,27 @@ export const Gallery: Story = {
244
441
  components: { CpAncillaryItem },
245
442
  setup() {
246
443
  const types = Object.values(CpAncillaryItemTypes)
247
- const quantities = ref(Object.fromEntries(types.map((type) => [type, 0])))
248
- return { types, quantities, galleryCellStyle, docLabelStyle, docRowWrapStyle }
444
+ const cardQuantities = ref(Object.fromEntries(types.map((type) => [type, 0])))
445
+ const listQuantities = ref(Object.fromEntries(types.map((type) => [type, 0])))
446
+ return { types, cardQuantities, listQuantities, galleryCellStyle, docLabelStyle, docRowWrapStyle }
249
447
  },
250
448
  template: `
251
449
  <div :style="docRowWrapStyle">
252
450
  <div v-for="type in types" :key="type" :style="galleryCellStyle">
253
451
  <span :style="docLabelStyle">{{ type }}</span>
254
452
  <CpAncillaryItem
255
- v-model:quantity="quantities[type]"
453
+ v-model:quantity="cardQuantities[type]"
454
+ :title="type"
455
+ description="Sample description"
456
+ :type="type"
457
+ :max-quantity="5"
458
+ max-quantity-label="max. 5"
459
+ :price="1000"
460
+ currency="EUR"
461
+ />
462
+ <span :style="docLabelStyle">{{ type }} — list</span>
463
+ <CpAncillaryItem
464
+ v-model:quantity="listQuantities[type]"
256
465
  :title="type"
257
466
  description="Sample description"
258
467
  :type="type"
@@ -260,6 +469,7 @@ export const Gallery: Story = {
260
469
  max-quantity-label="max. 5"
261
470
  :price="1000"
262
471
  currency="EUR"
472
+ layout="list"
263
473
  />
264
474
  </div>
265
475
  </div>