Skip to content

chore: new photos on webinar#200

Merged
hmbanan666 merged 1 commit into
mainfrom
webinar-up
Oct 6, 2025
Merged

chore: new photos on webinar#200
hmbanan666 merged 1 commit into
mainfrom
webinar-up

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Oct 6, 2025

Summary by CodeRabbit

  • New Features

    • Added sections: ForWhom, WillKnow, Reviews (with animated review cards), Register, and an alternate scrolling divider.
    • Revamped Countdown with two-column layout, background visuals, and entrance animation.
    • Updated Hero with new title, details, and registration button.
    • Reorganized homepage flow to include new sections and adjust divider placement.
  • Style

    • Changed divider colors and icons; updated carousel behavior.
    • Centered speaker card text.
    • Refined header navigation spacing and link styling.

@hmbanan666 hmbanan666 self-assigned this Oct 6, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 6, 2025

Walkthrough

Adds and reorganizes webinar UI: introduces several new components (ForWhom, WillKnow, Register, Reviews, InfiniteTitlesDividerReversed, ReviewCard), revises existing components (Countdown, Header, Hero, InfiniteTitlesDivider, SpeakerCard, Speakers), and restructures pages/index.vue to use the new components and updated section order. Multiple components add visibility-driven entrance animations.

Changes

Cohort / File(s) Summary of changes
Visibility-driven sections and cards
apps/webinar/app/components/ForWhom.vue, .../WillKnow.vue, .../Register.vue, .../ReviewCard.vue, .../Countdown.vue, .../Reviews.vue
Added new sections/cards using template refs and useElementVisibility to toggle isVisible for entrance motion classes; Countdown refactored layout with reveal logic; Reviews lists ReviewCard items with local data.
Carousel dividers
apps/webinar/app/components/InfiniteTitlesDivider.vue, .../InfiniteTitlesDividerReversed.vue
Updated existing divider: color change, removed loop, switched to UIcon, modified items, onMounted duplication. Added reversed variant with red bar, negative auto-scroll, and expanded items via repeated duplication.
Header, hero, speakers
apps/webinar/app/components/Header.vue, .../Hero.vue, .../SpeakerCard.vue, .../Speakers.vue
Header switches to UNavigationMenu with ui config; Hero replaces UPageHero props with custom content and CTA; SpeakerCard centers text; Speakers updates title/description and ui mapping.
Page composition
apps/webinar/app/pages/index.vue
Replaced inline sections with new components (ForWhom, WillKnow), added InfiniteTitlesDividerReversed, Reviews, Register, repositioned InfiniteTitlesDivider; removed About and Results; dropped PageFeatureProps and related arrays.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Page as pages/index.vue
  participant Sections as ForWhom / WillKnow / Register / Countdown / ReviewCard
  participant Vis as useElementVisibility
  participant UI as Motion Classes

  User->>Page: Load webinar page
  Page->>Sections: Render components
  loop On viewport scroll/resize
    Sections->>Vis: Observe target ref visibility
    Vis-->>Sections: targetIsVisible = true/false
    alt targetIsVisible becomes true
      Sections->>UI: set isVisible = true
      UI-->>User: Apply entrance animation
    else not visible yet
      Sections->>UI: isVisible remains false
    end
  end
Loading
sequenceDiagram
  autonumber
  participant Divider as InfiniteTitlesDivider
  participant Reversed as InfiniteTitlesDividerReversed
  participant VM as onMounted
  participant Carousel as UCarousel

  VM->>Divider: Duplicate items once
  VM->>Reversed: Duplicate items x5
  Divider->>Carousel: Render items, no loop
  Reversed->>Carousel: Start at last item, negative auto-scroll
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • chore: some new blocks #196 — Touches the same webinar UI components (Header.vue, Hero.vue, SpeakerCard.vue, Speakers.vue, pages/index.vue), indicating overlapping edits.
  • feat: webinar app #181 — Prior component-level changes within apps/webinar/* that intersect with files updated here (e.g., InfiniteTitlesDivider, Header, Hero, pages/index.vue).
  • chore: update #197 — Modifies Countdown.vue and pages/index.vue with similar layout/composition updates, overlapping with this PR’s areas.

Poem

In scarlet bars our titles glide, ♥
I twitch my ears with scrolling pride.
When sections peek, they softly slide—
A hop, a bop, I coincide.
Reviews and speakers, side by side,
I tap “Register!” wide-eyed.
Carrots queued, I’m webinar-fied! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “chore: new photos on webinar” is misleading because the pull request primarily introduces and refactors multiple Vue components, page sections, and animation behaviors rather than adding or updating photos. As a result, the title does not accurately summarize the core changes in the changeset. Please update the PR title to clearly reflect the main changes, for example: “feat(webinar): overhaul page layout with new sections and reveal animations.”
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch webinar-up

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Oct 6, 2025

@hmbanan666 hmbanan666 merged commit a0d1846 into main Oct 6, 2025
7 of 8 checks passed
@hmbanan666 hmbanan666 deleted the webinar-up branch October 6, 2025 13:57
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
apps/webinar/app/components/InfiniteTitlesDividerReversed.vue (1)

37-42: Items duplication logic is duplicated.

This is the same items duplication pattern flagged in InfiniteTitlesDivider.vue. Please refer to the refactoring suggestion in that file's review.

apps/webinar/app/components/ReviewCard.vue (1)

34-44: Visibility animation pattern is duplicated.

This is the same visibility animation pattern flagged in Register.vue (lines 54-64). Please refer to the composable extraction suggestion in that file's review.

apps/webinar/app/components/WillKnow.vue (1)

59-70: Duplicate visibility logic.

This visibility-tracking pattern is identical to the one in Countdown.vue (lines 48-60). See the review comment on Countdown.vue for a refactoring suggestion to extract this into a reusable composable.

apps/webinar/app/components/ForWhom.vue (1)

47-58: Duplicate visibility logic.

This visibility-tracking pattern is identical to the one in Countdown.vue (lines 48-60) and WillKnow.vue (lines 59-69). See the review comment on Countdown.vue for a refactoring suggestion to extract this into a reusable composable.

🧹 Nitpick comments (4)
apps/webinar/app/components/InfiniteTitlesDivider.vue (2)

38-42: Extract the items duplication logic into a shared utility.

This items duplication pattern is identical in both InfiniteTitlesDivider.vue and InfiniteTitlesDividerReversed.vue. Additionally, the magic number 5 lacks explanation.

Create a utility function:

// utils/carousel.ts
export function duplicateCarouselItems<T>(items: T[], times: number = 5): T[] {
  const result = [...items]
  for (let i = 0; i < times; i++) {
    result.push(...items)
  }
  return result
}

Then simplify the component:

 onMounted(() => {
-  // add items
-  for (let i = 0; i < 5; i++) {
-    items.value.push(...items.value)
-  }
+  items.value = duplicateCarouselItems(items.value)
 })

Consider documenting why 5 duplications are needed (e.g., for smooth infinite scrolling).


20-24: Remove commented code.

The commented <img> block should be removed rather than left in the codebase. If there's a need to preserve it for reference, consider using version control history instead.

Apply this diff:

-        <!-- <img
-          src="/sushi-heart.svg"
-          alt=""
-          class="w-10 h-auto opacity-90"
-        > -->
apps/webinar/app/components/Hero.vue (1)

22-26: Consider extracting the webinar date and time to a configuration.

The date (30 октября 2025) and time (12:00-13:30) are hardcoded here and also appear in Register.vue (lines 26, 29). If the webinar date changes, you'll need to update multiple locations.

Create a webinar configuration:

// config/webinar.ts
export const WEBINAR_CONFIG = {
  date: '30 октября 2025',
  time: '12:00-13:30 (МСК)',
  registrationUrl: 'https://t.me/sign_up_for_webinars_bot',
} as const

Then use it in components:

<template>
  <h3 class="flex flex-row gap-2 items-center text-xl font-bold">
    <UIcon name="i-lucide-calendar" class="size-8 text-secondary" /> {{ WEBINAR_CONFIG.date }}
  </h3>
  <h3 class="flex flex-row gap-2 items-center text-xl font-bold">
    <UIcon name="i-lucide-clock" class="size-8 text-secondary" /> {{ WEBINAR_CONFIG.time }}
  </h3>
</template>

<script setup lang="ts">
import { WEBINAR_CONFIG } from '~/config/webinar'
</script>
apps/webinar/app/components/Reviews.vue (1)

7-11: Consider using a unique ID or index for the key.

Using review.user.name as the key (line 9) could cause collisions if two reviews share the same name. Consider adding a unique id field to each review object or use the array index as a fallback.

-      <ReviewCard
-        v-for="review in reviews"
-        :key="review.user.name"
-        :review="review"
-      />
+      <ReviewCard
+        v-for="(review, index) in reviews"
+        :key="review.id ?? index"
+        :review="review"
+      />

Then add an id field to each review object:

 const reviews = ref([{
+  id: 1,
   user: {
     name: 'Сергей',
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a46f89b and 6b6f5f4.

⛔ Files ignored due to path filters (7)
  • apps/webinar/public/bg-1.jpg is excluded by !**/*.jpg
  • apps/webinar/public/bg-2.jpg is excluded by !**/*.jpg
  • apps/webinar/public/bg-countdown.png is excluded by !**/*.png
  • apps/webinar/public/bg-know.png is excluded by !**/*.png
  • apps/webinar/public/bg-register.png is excluded by !**/*.png
  • apps/webinar/public/bg-whom.png is excluded by !**/*.png
  • apps/webinar/public/sushi-heart-256.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • apps/webinar/app/components/Countdown.vue (1 hunks)
  • apps/webinar/app/components/ForWhom.vue (1 hunks)
  • apps/webinar/app/components/Header.vue (1 hunks)
  • apps/webinar/app/components/Hero.vue (1 hunks)
  • apps/webinar/app/components/InfiniteTitlesDivider.vue (2 hunks)
  • apps/webinar/app/components/InfiniteTitlesDividerReversed.vue (1 hunks)
  • apps/webinar/app/components/Register.vue (1 hunks)
  • apps/webinar/app/components/ReviewCard.vue (1 hunks)
  • apps/webinar/app/components/Reviews.vue (1 hunks)
  • apps/webinar/app/components/SpeakerCard.vue (1 hunks)
  • apps/webinar/app/components/Speakers.vue (1 hunks)
  • apps/webinar/app/components/WillKnow.vue (1 hunks)
  • apps/webinar/app/pages/index.vue (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
apps/webinar/app/components/Header.vue (1)

3-10: LGTM!

The UNavigationMenu configuration with ui prop for item spacing and link styling is properly implemented.

apps/webinar/app/components/SpeakerCard.vue (1)

11-11: LGTM!

The centering of speaker details is a straightforward presentational improvement that aligns with the overall UI updates.

apps/webinar/app/components/Speakers.vue (1)

4-9: LGTM!

The updated title and description provide clearer context about the speakers, and the UI mapping correctly applies the new description styling.

apps/webinar/app/components/Register.vue (1)

34-35: Verify external link accessibility.
The registration button links to an external Telegram bot. Ensure that:

  • Users are informed they’re leaving the site
  • Links opened in a new tab (target="_blank") include rel="noopener noreferrer"

Confirm whether UButton applies rel="noopener noreferrer" by default; if not, add it explicitly.

apps/webinar/app/pages/index.vue (1)

1-21: LGTM!

The page restructuring integrates the new components cleanly. The content flow is logical and the component composition is appropriate.

Comment on lines +48 to +60
<script setup lang="ts">
const target = useTemplateRef<HTMLDivElement>('target')
const targetIsVisible = useElementVisibility(target)

const isVisible = ref(false)
watch(targetIsVisible, () => {
if (!targetIsVisible.value) {
return
}

isVisible.value = targetIsVisible.value
})
</script>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract duplicated visibility logic into a composable.

This exact visibility-tracking pattern (useTemplateRef → useElementVisibility → watch → isVisible flag) is duplicated across multiple components (Countdown.vue, ForWhom.vue lines 47-57, WillKnow.vue lines 59-69). This violates DRY and increases maintenance overhead.

Consider creating a composable:

// composables/useVisibilityAnimation.ts
export function useVisibilityAnimation(refName: string = 'target') {
  const target = useTemplateRef<HTMLDivElement>(refName)
  const targetIsVisible = useElementVisibility(target)
  const isVisible = ref(false)
  
  watch(targetIsVisible, () => {
    if (!targetIsVisible.value) {
      return
    }
    isVisible.value = targetIsVisible.value
  })
  
  return { target, isVisible }
}

Then simplify each component's script to:

-const target = useTemplateRef<HTMLDivElement>('target')
-const targetIsVisible = useElementVisibility(target)
-
-const isVisible = ref(false)
-watch(targetIsVisible, () => {
-  if (!targetIsVisible.value) {
-    return
-  }
-
-  isVisible.value = targetIsVisible.value
-})
+const { target, isVisible } = useVisibilityAnimation()

Comment on lines +54 to +64
const target = useTemplateRef<HTMLDivElement>('target')
const targetIsVisible = useElementVisibility(target)

const isVisible = ref(false)
watch(targetIsVisible, () => {
if (!targetIsVisible.value) {
return
}

isVisible.value = targetIsVisible.value
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract the visibility animation pattern into a composable.

This visibility animation pattern is duplicated across multiple components (Register.vue, ReviewCard.vue, and likely others mentioned in the AI summary). The same logic using useTemplateRef, useElementVisibility, and a watcher to set isVisible appears in at least three places.

Create a composable to eliminate this duplication:

// composables/useVisibilityAnimation.ts
export function useVisibilityAnimation() {
  const target = useTemplateRef<HTMLDivElement>('target')
  const targetIsVisible = useElementVisibility(target)
  
  const isVisible = ref(false)
  watch(targetIsVisible, () => {
    if (!targetIsVisible.value) {
      return
    }
    isVisible.value = targetIsVisible.value
  })
  
  return { target, isVisible }
}

Then simplify the component script:

-const target = useTemplateRef<HTMLDivElement>('target')
-const targetIsVisible = useElementVisibility(target)
-
-const isVisible = ref(false)
-watch(targetIsVisible, () => {
-  if (!targetIsVisible.value) {
-    return
-  }
-
-  isVisible.value = targetIsVisible.value
-})
+const { target, isVisible } = useVisibilityAnimation()

Comment on lines +17 to +47
const reviews = ref([{
user: {
name: 'Сергей',
description: 'Одинцово',
avatar: {
src: 'https://avatar.nextorders.ru/637024?emotion=8&gender=male&clothing=teal',
alt: '',
},
},
quote: 'Открыл суши-бар в августе 2025 года. На открытие ушел 1 месяц. В первый день выручка составила 70 тыс. руб. Считаю это неплохим показателем, учитывая, что один из поваров попал в аварию и мы вынуждены были справляться сами. Иначе выручки были бы еще больше.',
}, {
user: {
name: 'Надежда',
description: 'Зеленоградск',
avatar: {
src: 'https://avatar.nextorders.ru/219743?emotion=7&gender=female&clothing=violet',
alt: '',
},
},
quote: 'На открытие своего кафе в курортном городе у меня ушло 2 месяца. Мое кафе работает уже пару лет и приносит мне 3 млн.руб выручки в год. Треть из них моя чистая прибыль.',
}, {
user: {
name: 'Екатерина',
description: 'Гурьевск',
avatar: {
src: 'https://avatar.nextorders.ru/108633?emotion=9&gender=female&clothing=green',
alt: '',
},
},
quote: 'Я открыла свое заведение более 10 лет назад, которое работает и по сей день, принося, стабильный доход. Все бизнес процессы отточены, а управлению я уделяю не более 2-х часов в неделю.',
}])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add meaningful alt text for user avatars.

Lines 23, 33, and 43 have empty alt attributes. User avatars convey identity and should include descriptive alt text, such as alt: 'Фото Сергея' or similar.

Apply this diff:

   user: {
     name: 'Сергей',
     description: 'Одинцово',
     avatar: {
       src: 'https://avatar.nextorders.ru/637024?emotion=8&gender=male&clothing=teal',
-      alt: '',
+      alt: 'Фото Сергея',
     },
   },

Repeat for the other two reviews with appropriate names.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const reviews = ref([{
user: {
name: 'Сергей',
description: 'Одинцово',
avatar: {
src: 'https://avatar.nextorders.ru/637024?emotion=8&gender=male&clothing=teal',
alt: '',
},
},
quote: 'Открыл суши-бар в августе 2025 года. На открытие ушел 1 месяц. В первый день выручка составила 70 тыс. руб. Считаю это неплохим показателем, учитывая, что один из поваров попал в аварию и мы вынуждены были справляться сами. Иначе выручки были бы еще больше.',
}, {
user: {
name: 'Надежда',
description: 'Зеленоградск',
avatar: {
src: 'https://avatar.nextorders.ru/219743?emotion=7&gender=female&clothing=violet',
alt: '',
},
},
quote: 'На открытие своего кафе в курортном городе у меня ушло 2 месяца. Мое кафе работает уже пару лет и приносит мне 3 млн.руб выручки в год. Треть из них моя чистая прибыль.',
}, {
user: {
name: 'Екатерина',
description: 'Гурьевск',
avatar: {
src: 'https://avatar.nextorders.ru/108633?emotion=9&gender=female&clothing=green',
alt: '',
},
},
quote: 'Я открыла свое заведение более 10 лет назад, которое работает и по сей день, принося, стабильный доход. Все бизнес процессы отточены, а управлению я уделяю не более 2-х часов в неделю.',
}])
const reviews = ref([{
user: {
name: 'Сергей',
description: 'Одинцово',
avatar: {
src: 'https://avatar.nextorders.ru/637024?emotion=8&gender=male&clothing=teal',
alt: 'Фото Сергея',
},
},
quote: 'Открыл суши-бар в августе 2025 года. На открытие ушел 1 месяц. В первый день выручка составила 70 тыс. руб. Считаю это неплохим показателем, учитывая, что один из поваров попал в аварию и мы вынуждены были справляться сами. Иначе выручки были бы еще больше.',
}, {
user: {
name: 'Надежда',
description: 'Зеленоградск',
avatar: {
src: 'https://avatar.nextorders.ru/219743?emotion=7&gender=female&clothing=violet',
alt: 'Фото Надежды',
},
},
quote: 'На открытие своего кафе в курортном городе у меня ушло 2 месяца. Мое кафе работает уже пару лет и приносит мне 3 млн.руб выручки в год. Треть из них моя чистая прибыль.',
}, {
user: {
name: 'Екатерина',
description: 'Гурьевск',
avatar: {
src: 'https://avatar.nextorders.ru/108633?emotion=9&gender=female&clothing=green',
alt: 'Фото Екатерины',
},
},
quote: 'Я открыла свое заведение более 10 лет назад, которое работает и по сей день, принося, стабильный доход. Все бизнес процессы отточены, а управлению я уделяю не более 2-х часов в неделю.',
}])
🤖 Prompt for AI Agents
In apps/webinar/app/components/Reviews.vue around lines 17 to 47 the avatar
objects have empty alt attributes which should be meaningful for accessibility;
replace the empty alt strings with descriptive text for each user (e.g., for
Сергей set avatar.alt to "Фото Сергея", for Надежда set avatar.alt to "Фото
Надежды", and for Екатерина set avatar.alt to "Фото Екатерины") so each review's
avatar includes an appropriate alt value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant