erudit 3.0.0-dev.20 → 3.0.0-dev.21

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 (89) hide show
  1. package/app/app.vue +2 -0
  2. package/app/assets/icons/cameo-add.svg +3 -0
  3. package/app/assets/icons/diamond.svg +3 -0
  4. package/app/components/Avatar.vue +118 -0
  5. package/app/components/EruditLink.vue +17 -0
  6. package/app/components/SiteMain.vue +4 -4
  7. package/app/components/ads/Ads.vue +1 -3
  8. package/app/components/ads/AdsProviderYandex.vue +59 -22
  9. package/app/components/aside/AsideListItem.vue +21 -4
  10. package/app/components/aside/AsideMinor.vue +1 -1
  11. package/app/components/aside/major/SiteInfo.vue +4 -4
  12. package/app/components/aside/major/panes/Pages.vue +20 -1
  13. package/app/components/aside/major/panes/nav/fnav/FNavSeparator.vue +2 -2
  14. package/app/components/aside/minor/AsideMinorTopLink.vue +3 -4
  15. package/app/components/aside/minor/contributor/AsideMinorContributor.vue +9 -9
  16. package/app/components/aside/minor/topic/AsideMinorTopic.vue +3 -1
  17. package/app/components/aside/minor/topic/TopicContributors.vue +9 -3
  18. package/app/components/aside/minor/topic/TopicNav.vue +1 -1
  19. package/app/components/aside/minor/topic/TopicToc.vue +12 -13
  20. package/app/components/aside/minor/topic/TopicTocItem.vue +1 -14
  21. package/app/components/bitran/BitranContent.vue +0 -1
  22. package/app/components/contributor/ContributorListItem.vue +13 -5
  23. package/app/components/main/MainActionButton.vue +51 -0
  24. package/app/components/main/MainBitranContent.vue +11 -3
  25. package/app/components/main/MainBreadcrumb.vue +2 -6
  26. package/app/components/main/MainSection.vue +58 -0
  27. package/app/components/main/cameo/MainCameo.vue +135 -0
  28. package/app/components/main/cameo/MainCameoData.vue +232 -0
  29. package/app/components/main/content/ContentPopovers.vue +1 -1
  30. package/app/components/main/topic/MainTopic.vue +13 -18
  31. package/app/components/main/topic/TopicPartSwitch.vue +7 -12
  32. package/app/components/preview/PreviewFooterAction.vue +1 -1
  33. package/app/components/sponsor/SponsorTier1.vue +89 -0
  34. package/app/components/sponsor/SponsorTier2.vue +109 -0
  35. package/app/components/tree/TreeItem.vue +8 -4
  36. package/app/composables/asset.ts +12 -0
  37. package/app/composables/contentData.ts +1 -1
  38. package/app/composables/head.ts +24 -0
  39. package/app/composables/majorPane.ts +1 -0
  40. package/app/composables/url.ts +17 -7
  41. package/app/pages/contributor/[contributorId].vue +9 -6
  42. package/app/pages/contributors.vue +73 -72
  43. package/app/pages/group/[...groupId].vue +4 -3
  44. package/app/pages/sponsors.vue +95 -0
  45. package/app/plugins/prerender.server.ts +14 -2
  46. package/app/scripts/og.ts +2 -1
  47. package/const.ts +0 -1
  48. package/globals/cameo.ts +5 -0
  49. package/globals/register.ts +5 -0
  50. package/globals/sponsor.ts +17 -0
  51. package/languages/en.ts +8 -3
  52. package/languages/ru.ts +8 -3
  53. package/module/imports.ts +13 -6
  54. package/nuxt.config.ts +2 -7
  55. package/package.json +4 -4
  56. package/server/api/cameo/data/[cameoId].ts +42 -0
  57. package/server/api/cameo/ids.ts +5 -0
  58. package/server/api/prerender/assets/cameo.ts +14 -0
  59. package/server/api/prerender/assets/contributor.ts +12 -0
  60. package/server/api/prerender/assets/sponsor.ts +13 -0
  61. package/server/api/prerender/cameos.ts +12 -0
  62. package/server/api/{prerender.ts → prerender/language.ts} +3 -13
  63. package/server/api/sponsor/cameo/data/[sponsorId].ts +51 -0
  64. package/server/api/sponsor/cameo/ids.ts +5 -0
  65. package/server/api/sponsor/count.ts +5 -0
  66. package/server/api/sponsor/list.ts +36 -0
  67. package/server/plugin/build/process.ts +2 -0
  68. package/server/plugin/build/rebuild.ts +2 -0
  69. package/server/plugin/global.ts +2 -0
  70. package/server/plugin/repository/cameo.ts +16 -0
  71. package/server/plugin/repository/contributor.ts +35 -4
  72. package/server/plugin/sponsor/build.ts +82 -0
  73. package/server/plugin/sponsor/index.ts +5 -0
  74. package/server/plugin/sponsor/repository.ts +56 -0
  75. package/server/routes/asset/[...assetPath].ts +34 -0
  76. package/server/routes/robots.txt.ts +9 -0
  77. package/server/routes/sitemap.xml.ts +103 -0
  78. package/shared/asset.ts +0 -5
  79. package/shared/contributor.ts +1 -0
  80. package/shared/link.ts +4 -4
  81. package/shared/types/language.ts +6 -1
  82. package/test/utils/url.test.ts +99 -0
  83. package/utils/ext.ts +41 -0
  84. package/utils/url.ts +23 -0
  85. package/app/components/contributor/ContributorAvatar.vue +0 -45
  86. package/app/components/main/content/ContentSection.vue +0 -45
  87. package/app/public/user.svg +0 -10
  88. package/app/scripts/aside/minor/topic.ts +0 -3
  89. package/utils/slash.ts +0 -11
@@ -0,0 +1,89 @@
1
+ <script lang="ts" setup>
2
+ import type { Tier1Sponsor } from '@erudit-js/cog/schema';
3
+
4
+ const props = defineProps<{
5
+ sponsor: Tier1Sponsor;
6
+ }>();
7
+
8
+ const sponsorColor = computed(() => {
9
+ return props.sponsor.color ?? stringColor(props.sponsor.sponsorId);
10
+ });
11
+ </script>
12
+
13
+ <template>
14
+ <a
15
+ :href="props.sponsor.link"
16
+ target="_blank"
17
+ rel="noopener noreferrer"
18
+ :class="$style.sponsorItem"
19
+ :style="{ '--sponsorColor': sponsorColor }"
20
+ >
21
+ <Avatar
22
+ icon="diamond"
23
+ :src="sponsor.avatar"
24
+ :color="sponsorColor"
25
+ :class="$style.avatar"
26
+ :styling="{ glow: true }"
27
+ />
28
+ <div>
29
+ <div :class="$style.main">
30
+ <MyRuntimeIcon
31
+ v-if="sponsor.icon"
32
+ name="sponsor-icon"
33
+ :svg="sponsor.icon"
34
+ :class="$style.icon"
35
+ />
36
+ <span :class="$style.name">{{ sponsor.name }}</span>
37
+ </div>
38
+ <div v-if="sponsor.slogan" :class="$style.slogan">
39
+ {{ sponsor.slogan }}
40
+ </div>
41
+ </div>
42
+ </a>
43
+ </template>
44
+
45
+ <style lang="scss" module>
46
+ @use '$/def/bp';
47
+
48
+ .sponsorItem {
49
+ display: flex;
50
+ gap: var(--gap);
51
+ align-items: center;
52
+ padding: var(--gap);
53
+ font-size: 0.9em;
54
+ border-radius: 5px;
55
+ background: transparent;
56
+ text-decoration: none;
57
+
58
+ @include transition(background);
59
+
60
+ @include bp.below('mobile') {
61
+ border-radius: 0;
62
+ }
63
+
64
+ &:hover {
65
+ background: color-mix(in srgb, var(--bgMain), var(--sponsorColor) 12%);
66
+ }
67
+
68
+ .avatar {
69
+ --avatarSize: 50px;
70
+ flex-shrink: 0;
71
+ }
72
+
73
+ .main {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 8px;
77
+ font-weight: 600;
78
+ color: color-mix(in srgb, var(--text), var(--sponsorColor) 50%);
79
+
80
+ .icon {
81
+ opacity: 0.6;
82
+ }
83
+ }
84
+
85
+ .slogan {
86
+ color: var(--textMuted);
87
+ }
88
+ }
89
+ </style>
@@ -0,0 +1,109 @@
1
+ <script lang="ts" setup>
2
+ import type { Tier2Sponsor } from '@erudit-js/cog/schema';
3
+
4
+ const props = defineProps<{
5
+ sponsor: Tier2Sponsor;
6
+ }>();
7
+
8
+ const sponsorColor = computed(() => {
9
+ return props.sponsor.color ?? stringColor(props.sponsor.sponsorId);
10
+ });
11
+ </script>
12
+
13
+ <template>
14
+ <a
15
+ :href="props.sponsor.link"
16
+ target="_blank"
17
+ rel="noopener noreferrer"
18
+ :class="$style.sponsorItem"
19
+ :style="{ '--sponsorColor': sponsorColor }"
20
+ >
21
+ <div :class="$style.avatarWrapper">
22
+ <Avatar
23
+ icon="diamond"
24
+ :src="sponsor.avatar"
25
+ :color="sponsorColor"
26
+ :class="$style.avatar"
27
+ :styling="{ glow: true, border: true }"
28
+ />
29
+ <MyRuntimeIcon
30
+ v-if="sponsor.icon"
31
+ name="sponsor-icon"
32
+ :svg="sponsor.icon"
33
+ :class="$style.icon"
34
+ />
35
+ </div>
36
+ <div :class="$style.info">
37
+ <div :class="$style.name">{{ sponsor.name }}</div>
38
+ <div v-if="sponsor.slogan" :class="$style.slogan">
39
+ {{ sponsor.slogan }}
40
+ </div>
41
+ </div>
42
+ </a>
43
+ </template>
44
+
45
+ <style lang="scss" module>
46
+ @use '$/def/bp';
47
+
48
+ .sponsorItem {
49
+ display: flex;
50
+ flex-direction: column;
51
+ align-items: center;
52
+ gap: 22px;
53
+ padding: var(--gap);
54
+ border-radius: 5px;
55
+ background: transparent;
56
+ text-decoration: none;
57
+ text-align: center;
58
+
59
+ @include transition(background);
60
+
61
+ @include bp.below('mobile') {
62
+ border-radius: 0;
63
+ }
64
+
65
+ &:hover {
66
+ background: color-mix(in srgb, var(--bgMain), var(--sponsorColor) 10%);
67
+ }
68
+
69
+ .avatarWrapper {
70
+ position: relative;
71
+
72
+ .avatar {
73
+ --avatarSize: 100px;
74
+ }
75
+
76
+ .icon {
77
+ --iconSize: 26px;
78
+
79
+ position: absolute;
80
+ bottom: calc(-1 * var(--iconSize) / 2);
81
+ left: 50%;
82
+ transform: translateX(-50%);
83
+
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+
88
+ color: var(--sponsorColor);
89
+ font-size: calc(var(--iconSize) / 1.8);
90
+ width: var(--iconSize);
91
+ height: var(--iconSize);
92
+ border-radius: 50%;
93
+ border: 1.5px solid var(--sponsorColor);
94
+ background: var(--bgMain);
95
+ }
96
+ }
97
+
98
+ .info {
99
+ .name {
100
+ font-weight: 600;
101
+ color: color-mix(in srgb, var(--text), var(--sponsorColor) 50%);
102
+ }
103
+
104
+ .slogan {
105
+ color: var(--textMuted);
106
+ }
107
+ }
108
+ }
109
+ </style>
@@ -13,8 +13,7 @@ defineProps<{
13
13
  </script>
14
14
 
15
15
  <template>
16
- <NuxtLink
17
- :prefetch="false"
16
+ <EruditLink
18
17
  :to="link"
19
18
  :class="[
20
19
  $style.treeItem,
@@ -24,12 +23,17 @@ defineProps<{
24
23
  :style="{ ['--_level']: level ?? 0 }"
25
24
  >
26
25
  <MyIcon v-if="icon" :class="$style.icon" :name="icon" />
27
- <MyRuntimeIcon v-else :class="$style.icon" name="tree-item-icon" :svg />
26
+ <MyRuntimeIcon
27
+ v-else
28
+ :class="$style.icon"
29
+ name="tree-item-icon"
30
+ :svg="svg!"
31
+ />
28
32
  <div :class="$style.main">{{ label }}</div>
29
33
  <div v-if="$slots.default" :class="$style.after">
30
34
  <slot></slot>
31
35
  </div>
32
- </NuxtLink>
36
+ </EruditLink>
33
37
  </template>
34
38
 
35
39
  <style lang="scss" module>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @param path Path relative to `PROJECT_DIR`
3
+ */
4
+ export function useAssetRoute() {
5
+ const useBaseUrl = useBaseUrlPath();
6
+ return (path: string) => {
7
+ if (!path.startsWith('/asset/')) {
8
+ path = '/asset/' + path.replace(/^\/+/, '');
9
+ }
10
+ return useBaseUrl(path);
11
+ };
12
+ }
@@ -1,4 +1,4 @@
1
- import { trailingSlash } from '@erudit/utils/slash';
1
+ import { trailingSlash } from '@erudit/utils/url';
2
2
  import type { ContentData } from '@shared/content/data';
3
3
 
4
4
  export function useContentData<T extends ContentData>() {
@@ -0,0 +1,24 @@
1
+ import eruditConfig from '#erudit/config';
2
+
3
+ interface EruditHead {
4
+ title: string;
5
+ description?: string;
6
+ }
7
+
8
+ export function useEruditHead(head: EruditHead) {
9
+ const siteTitle = getSiteTitle();
10
+ const title = head.title ? `${head.title} - ${siteTitle}` : siteTitle;
11
+
12
+ useHead({ title });
13
+
14
+ useSeoMeta({
15
+ title,
16
+ ogTitle: title,
17
+ description: head.description,
18
+ ogDescription: head.description,
19
+ });
20
+ }
21
+
22
+ function getSiteTitle() {
23
+ return eruditConfig.seo?.title || eruditConfig.site?.title || 'Erudit';
24
+ }
@@ -47,6 +47,7 @@ export function useMajorPane() {
47
47
  switch (true) {
48
48
  case route.path.startsWith('/contributors'):
49
49
  case route.path.startsWith('/contributor/'):
50
+ case route.path === '/sponsors/':
50
51
  return 'pages';
51
52
  default:
52
53
  return 'index';
@@ -1,12 +1,22 @@
1
1
  import eruditConfig from '#erudit/config';
2
+ import { trailingSlash, normalizeUrl } from '@erudit/utils/url';
2
3
 
3
4
  export function useBaseUrlPath() {
4
5
  const runtimeConfig = useRuntimeConfig();
6
+ const baseURL = runtimeConfig.app.baseURL;
7
+
5
8
  return (path: string) => {
6
- const baseURL = runtimeConfig.app.baseURL;
7
- if (path.startsWith(baseURL)) return path;
8
- else if (path.startsWith('/')) return baseURL + path.substring(1);
9
- else return path;
9
+ const normalizedPath = normalizeUrl(path);
10
+
11
+ if (normalizedPath.startsWith(baseURL)) {
12
+ return normalizedPath;
13
+ }
14
+
15
+ if (normalizedPath.startsWith('/')) {
16
+ return normalizeUrl(baseURL + normalizedPath.substring(1));
17
+ }
18
+
19
+ return normalizedPath;
10
20
  };
11
21
  }
12
22
 
@@ -18,7 +28,7 @@ export function useSiteUrl() {
18
28
  if (!import.meta.dev && eruditConfig.site?.buildUrl)
19
29
  return eruditConfig.site.buildUrl + baseUrl.slice(0, -1);
20
30
 
21
- return url.origin;
31
+ return normalizeUrl(trailingSlash(url.origin, true));
22
32
  }
23
33
 
24
34
  export function usePageUrl() {
@@ -27,7 +37,7 @@ export function usePageUrl() {
27
37
 
28
38
  return computed(() => {
29
39
  if (route.path === '/') return siteUrl;
30
-
31
- return siteUrl + route.path;
40
+ const fullUrl = siteUrl + route.path;
41
+ return normalizeUrl(trailingSlash(fullUrl, true));
32
42
  });
33
43
  }
@@ -1,7 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import eruditConfig from '#erudit/config';
3
3
  import { type PageContributor } from '@shared/contributor';
4
- import ContentSection from '@app/components/main/content/ContentSection.vue';
5
4
 
6
5
  const route = useRoute();
7
6
  const nuxtApp = useNuxtApp();
@@ -96,10 +95,16 @@ useSeoMeta({
96
95
  :style="{ ['--contributorColor']: contributorColor }"
97
96
  >
98
97
  <div style="position: relative">
99
- <ContributorAvatar
98
+ <Avatar
99
+ icon="user"
100
+ :src="
101
+ contributor.avatar
102
+ ? `/contributors/${contributor.avatar}`
103
+ : undefined
104
+ "
100
105
  :class="$style.avatar"
101
- :contributorId="contributor.contributorId"
102
- :avatar="contributor.avatar"
106
+ :color="stringColor(contributor.contributorId)"
107
+ :styling="{ glow: true, border: true }"
103
108
  />
104
109
  <MyIcon
105
110
  v-if="contributor.isEditor"
@@ -147,8 +152,6 @@ useSeoMeta({
147
152
 
148
153
  .avatar {
149
154
  --_avatarSize: 110px;
150
- border: 2px solid var(--bgMain);
151
- outline: 2px solid var(--contributorColor);
152
155
  box-shadow: 0 0 100px 100px
153
156
  color-mix(in srgb, var(--contributorColor), transparent 90%);
154
157
  }
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import eruditConfig from '#erudit/config';
3
+ import { createContributorLink } from '@shared/link';
3
4
 
4
5
  const { data: contributorList } = await useFetch(`/api/contributor/list`, {
5
6
  key: 'contributor-list',
@@ -13,22 +14,13 @@ const phrase = await usePhrases(
13
14
  'contributors',
14
15
  'contributors_page_description',
15
16
  'contributors_page_invite',
17
+ 'become_contributor',
16
18
  'contributions_explain',
17
19
  'editor',
18
20
  );
19
21
 
20
- useHead({
21
- title:
22
- phrase.contributors +
23
- ' - ' +
24
- (eruditConfig.seo?.title || eruditConfig.site?.title),
25
- });
26
-
27
- useSeoMeta({
28
- title:
29
- phrase.contributors +
30
- ' - ' +
31
- (eruditConfig.seo?.title || eruditConfig.site?.title),
22
+ useEruditHead({
23
+ title: phrase.contributors,
32
24
  description: phrase.contributors_page_description,
33
25
  });
34
26
 
@@ -36,11 +28,7 @@ const fullDescription = (() => {
36
28
  let _description = phrase.contributors_page_description;
37
29
 
38
30
  if (eruditConfig.content?.howToImproveLink) {
39
- _description +=
40
- ' ' +
41
- phrase.contributors_page_invite(
42
- eruditConfig.content.howToImproveLink,
43
- );
31
+ _description += ' ' + phrase.contributors_page_invite;
44
32
  }
45
33
 
46
34
  return _description;
@@ -54,69 +42,80 @@ const fullDescription = (() => {
54
42
  :html="true"
55
43
  :description="fullDescription"
56
44
  />
57
- <section :class="$style.contributors">
58
- <NuxtLink
59
- :to="`/contributor/${contributor.contributorId}`"
60
- :prefetch="false"
61
- v-for="(contributor, i) of contributorList"
62
- :class="$style.contributor"
63
- :style="{
64
- '--contributorColor': stringColor(contributor.contributorId),
65
- }"
66
- >
67
- <ContributorAvatar
68
- :contributorId="contributor.contributorId"
69
- :avatar="contributor.avatar"
70
- :class="$style.avatar"
71
- />
72
- <div :class="$style.info">
73
- <div :class="$style.main">
74
- <div :class="$style.position">
75
- <template v-if="contributor.isEditor">
76
- <MyIcon
77
- :title="phrase.editor"
78
- :class="$style.editor"
79
- name="graduation"
80
- />
81
- </template>
82
- <template v-else>
83
- {{ i - editorsNumber + 1 }}.
84
- </template>
45
+ <MainActionButton
46
+ v-if="eruditConfig.content?.howToImproveLink"
47
+ icon="users"
48
+ :label="phrase.become_contributor"
49
+ :link="eruditConfig.content.howToImproveLink"
50
+ />
51
+ <MainSection>
52
+ <section :class="$style.contributors">
53
+ <EruditLink
54
+ :to="createContributorLink(contributor.contributorId)"
55
+ :prefetch="false"
56
+ v-for="(contributor, i) of contributorList"
57
+ :class="$style.contributor"
58
+ :style="{
59
+ '--contributorColor': stringColor(
60
+ contributor.contributorId,
61
+ ),
62
+ }"
63
+ >
64
+ <Avatar
65
+ icon="user"
66
+ :src="
67
+ contributor.avatar
68
+ ? `/contributors/${contributor.avatar}`
69
+ : undefined
70
+ "
71
+ :class="$style.avatar"
72
+ :color="stringColor(contributor.contributorId)"
73
+ :styling="{ glow: true, border: false }"
74
+ />
75
+ <div :class="$style.info">
76
+ <div :class="$style.main">
77
+ <div :class="$style.position">
78
+ <template v-if="contributor.isEditor">
79
+ <MyIcon
80
+ :title="phrase.editor"
81
+ :class="$style.editor"
82
+ name="graduation"
83
+ />
84
+ </template>
85
+ <template v-else>
86
+ {{ i - editorsNumber + 1 }}.
87
+ </template>
88
+ </div>
89
+ <div :class="$style.name">
90
+ {{
91
+ contributor.displayName ||
92
+ contributor.contributorId
93
+ }}
94
+ </div>
85
95
  </div>
86
- <div :class="$style.name">
96
+ <div
97
+ v-if="contributor.contributions"
98
+ :class="$style.contributions"
99
+ >
87
100
  {{
88
- contributor.displayName || contributor.contributorId
101
+ phrase.contributions_explain(
102
+ contributor.contributions,
103
+ )
89
104
  }}
90
105
  </div>
91
106
  </div>
92
- <div :class="$style.contributions">
93
- {{
94
- phrase.contributions_explain(contributor.contributions)
95
- }}
96
- </div>
97
- </div>
98
- </NuxtLink>
99
- </section>
107
+ </EruditLink>
108
+ </section>
109
+ </MainSection>
100
110
  </template>
101
111
 
102
112
  <style lang="scss" module>
103
113
  @use '$/def/bp';
104
114
 
105
- .pageDescription a {
106
- color: inherit;
107
- text-decoration-style: dashed;
108
- text-decoration-color: var(--textDimmed);
109
-
110
- &:hover {
111
- text-decoration-style: solid;
112
- text-decoration-color: var(--text);
113
- }
114
- }
115
-
116
115
  .contributors {
117
- padding: var(--_pMainY) var(--_pMainX);
116
+ padding: 0 var(--_pMainX);
118
117
  display: grid;
119
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
118
+ grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
120
119
  gap: var(--gap);
121
120
 
122
121
  @include bp.below('mobile') {
@@ -135,7 +134,11 @@ const fullDescription = (() => {
135
134
  @include transition(background);
136
135
 
137
136
  &:hover {
138
- background: var(--bgAccent);
137
+ background: color-mix(
138
+ in srgb,
139
+ var(--bgMain),
140
+ var(--contributorColor) 12%
141
+ );
139
142
  }
140
143
 
141
144
  @include bp.below('mobile') {
@@ -143,10 +146,8 @@ const fullDescription = (() => {
143
146
  }
144
147
 
145
148
  .avatar {
146
- --_avatarSize: 50px;
149
+ --avatarSize: 50px;
147
150
  flex-shrink: 0;
148
- box-shadow: 0 0 5px 5px
149
- color-mix(in srgb, var(--contributorColor), transparent 85%);
150
151
  }
151
152
 
152
153
  .info {
@@ -8,7 +8,6 @@ import { locationIcon } from '@erudit/shared/icons';
8
8
  import ContentBreadcrumb from '@app/components/main/content/ContentBreadcrumb.vue';
9
9
  import ContentDecoration from '@app/components/main/content/ContentDecoration.vue';
10
10
  import ContentPopovers from '@app/components/main/content/ContentPopovers.vue';
11
- import ContentSection from '@app/components/main/content/ContentSection.vue';
12
11
 
13
12
  const location = useBitranLocation() as Ref<BitranLocation>;
14
13
 
@@ -42,11 +41,13 @@ const phrase = await usePhrases('group');
42
41
 
43
42
  <ContentPopovers :generic="groupData.generic" />
44
43
 
44
+ <MainCameo />
45
+
45
46
  <div style="clear: both"></div>
46
47
 
47
48
  <MainBitranContent :location />
48
49
 
49
- <ContentSection v-if="adsAllowed() && eruditConfig.ads?.bottom">
50
+ <MainSection v-if="adsAllowed() && eruditConfig.ads?.bottom">
50
51
  <AdsBannerBottom />
51
- </ContentSection>
52
+ </MainSection>
52
53
  </template>
@@ -0,0 +1,95 @@
1
+ <script lang="ts" setup>
2
+ import eruditConfig from '#erudit/config';
3
+
4
+ const { data: sponsors } = await useAsyncData(
5
+ 'sponsors',
6
+ () => $fetch('/api/sponsor/list'),
7
+ { deep: false },
8
+ );
9
+
10
+ const phrase = await usePhrases(
11
+ 'sponsors',
12
+ 'sponsors_description',
13
+ 'become_sponsor',
14
+ );
15
+
16
+ useEruditHead({
17
+ title: phrase.sponsors,
18
+ description: phrase.sponsors_description,
19
+ });
20
+ </script>
21
+
22
+ <template>
23
+ <MainTitle icon="diamond" :title="phrase.sponsors" />
24
+ <MainDescription :description="phrase.sponsors_description" />
25
+ <MainActionButton
26
+ v-if="eruditConfig.content?.howToImproveLink"
27
+ icon="diamond"
28
+ :label="phrase.become_sponsor"
29
+ :link="eruditConfig.content.howToImproveLink"
30
+ />
31
+ <MainSection :class="$style.sponsorSection" v-if="sponsors?.tier2?.length">
32
+ <template v-slot:header>
33
+ <h2 :class="$style.tierHeading">
34
+ {{ eruditConfig.sponsors!.tier2Label }}
35
+ </h2>
36
+ </template>
37
+ <div :class="$style.tier2Grid">
38
+ <SponsorTier2 v-for="sponsor of sponsors.tier2" :sponsor />
39
+ </div>
40
+ </MainSection>
41
+ <MainSection :class="$style.sponsorSection" v-if="sponsors?.tier1?.length">
42
+ <template v-slot:header>
43
+ <h2 :class="$style.tierHeading">
44
+ {{ eruditConfig.sponsors!.tier1Label }}
45
+ </h2>
46
+ </template>
47
+ <div :class="$style.tier1Grid">
48
+ <SponsorTier1 v-for="sponsor of sponsors.tier1" :sponsor />
49
+ </div>
50
+ </MainSection>
51
+ </template>
52
+
53
+ <style lang="scss" module>
54
+ @use '$/def/bp';
55
+
56
+ .tierHeading {
57
+ padding: var(--gap) var(--_pMainX);
58
+ font-size: 1.4em;
59
+
60
+ @include bp.below('mobile') {
61
+ text-align: center;
62
+ }
63
+ }
64
+
65
+ .sponsorSection:nth-child(even of .sponsorSection) {
66
+ .tierHeading {
67
+ padding-bottom: 0;
68
+ padding-top: calc(1.5 * var(--gap));
69
+ }
70
+ }
71
+
72
+ .tier2Grid {
73
+ padding: 0 var(--_pMainX);
74
+ display: grid;
75
+ justify-content: center;
76
+ grid-template-columns: repeat(auto-fit, 310px);
77
+ gap: var(--gap);
78
+
79
+ @include bp.below('mobile') {
80
+ padding: var(--_pMainY) 0;
81
+ }
82
+ }
83
+
84
+ .tier1Grid {
85
+ padding: 0 var(--_pMainX);
86
+ display: grid;
87
+ grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
88
+ gap: var(--gap);
89
+
90
+ @include bp.below('mobile') {
91
+ padding: var(--_pMainY) 0;
92
+ gap: 0;
93
+ }
94
+ }
95
+ </style>