একটি 3D গেম মেনু উপাদান তৈরি করা

কিভাবে একটি প্রতিক্রিয়াশীল, অভিযোজিত, এবং অ্যাক্সেসযোগ্য 3D গেম মেনু তৈরি করতে হয় তার একটি মৌলিক ওভারভিউ।

এই পোস্টে আমি একটি 3D গেম মেনু উপাদান তৈরি করার উপায় সম্পর্কে চিন্তাভাবনা ভাগ করতে চাই। ডেমো চেষ্টা করুন.

ডেমো

আপনি যদি ভিডিও পছন্দ করেন তবে এখানে এই পোস্টটির একটি YouTube সংস্করণ রয়েছে:

ওভারভিউ

ভিডিও গেমগুলি প্রায়শই ব্যবহারকারীদের একটি সৃজনশীল এবং অস্বাভাবিক মেনু, অ্যানিমেটেড এবং 3D স্পেসে উপস্থাপন করে। এটি নতুন AR/VR গেমগুলিতে জনপ্রিয় যাতে মেনুটিকে মহাকাশে ভাসতে দেখা যায়৷ আজ আমরা এই প্রভাবের প্রয়োজনীয় জিনিসগুলি আবার তৈরি করব তবে একটি অভিযোজিত রঙের স্কিম এবং ব্যবহারকারীদের জন্য থাকার ব্যবস্থার সাথে যারা কম গতি পছন্দ করেন।

এইচটিএমএল

একটি গেম মেনু হল বোতামগুলির একটি তালিকা। HTML এ এটি উপস্থাপন করার সর্বোত্তম উপায় হল নিম্নরূপ:

<ul class="threeD-button-set">
  <li><button>New Game</button></li>
  <li><button>Continue</button></li>
  <li><button>Online</button></li>
  <li><button>Settings</button></li>
  <li><button>Quit</button></li>
</ul>

বোতামগুলির একটি তালিকা স্ক্রিন রিডার প্রযুক্তি এবং জাভাস্ক্রিপ্ট বা CSS ছাড়া কাজ করার জন্য নিজেকে ভালভাবে ঘোষণা করবে।

আইটেম হিসাবে নিয়মিত বোতাম সহ একটি খুব সাধারণ চেহারা বুলেট তালিকা.

সিএসএস

বোতাম তালিকা স্টাইল করা নিম্নলিখিত উচ্চ স্তরের ধাপে বিভক্ত হয়:

  1. কাস্টম বৈশিষ্ট্য সেট আপ করা হচ্ছে.
  2. একটি ফ্লেক্সবক্স লেআউট।
  3. আলংকারিক ছদ্ম-উপাদান সহ একটি কাস্টম বোতাম।
  4. 3D স্পেসে উপাদান স্থাপন.

কাস্টম বৈশিষ্ট্য ওভারভিউ

কাস্টম বৈশিষ্ট্যগুলি অন্যথায় এলোমেলো চেহারার মানগুলির অর্থপূর্ণ নাম দিয়ে, বারবার কোড এড়ানো এবং শিশুদের মধ্যে মানগুলি ভাগ করে নেওয়ার মাধ্যমে মানগুলিকে দ্ব্যর্থিত করতে সহায়তা করে৷

নীচে মিডিয়া কোয়েরিগুলি CSS ভেরিয়েবল হিসাবে সংরক্ষিত রয়েছে, যা কাস্টম মিডিয়া নামেও পরিচিত। এগুলি বিশ্বব্যাপী এবং কোড সংক্ষিপ্ত এবং সুস্পষ্ট রাখার জন্য বিভিন্ন নির্বাচক জুড়ে ব্যবহার করা হবে। গেম মেনু উপাদানটি গতি পছন্দ , সিস্টেমের রঙের স্কিম এবং ডিসপ্লের রঙ পরিসরের ক্ষমতা ব্যবহার করে।

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);

নিচের কাস্টম বৈশিষ্ট্যগুলি রঙের স্কিম পরিচালনা করে এবং গেম মেনুকে হোভার করার জন্য ইন্টারেক্টিভ করার জন্য মাউসের অবস্থানগত মান ধরে রাখে। কাস্টম বৈশিষ্ট্যের নামকরণ কোডকে সুস্পষ্ট করতে সাহায্য করে কারণ এটি মানের ব্যবহারের ক্ষেত্রে বা মানের ফলাফলের জন্য একটি বন্ধুত্বপূর্ণ নাম প্রকাশ করে।

.threeD-button-set {
  --y:;
  --x:;
  --distance: 1px;
  --theme: hsl(180 100% 50%);
  --theme-bg: hsl(180 100% 50% / 25%);
  --theme-bg-hover: hsl(180 100% 50% / 40%);
  --theme-text: white;
  --theme-shadow: hsl(180 100% 10% / 25%);

  --_max-rotateY: 10deg;
  --_max-rotateX: 15deg;
  --_btn-bg: var(--theme-bg);
  --_btn-bg-hover: var(--theme-bg-hover);
  --_btn-text: var(--theme-text);
  --_btn-text-shadow: var(--theme-shadow);
  --_bounce-ease: cubic-bezier(.5, 1.75, .75, 1.25);

  @media (--dark) {
    --theme: hsl(255 53% 50%);
    --theme-bg: hsl(255 53% 71% / 25%);
    --theme-bg-hover: hsl(255 53% 50% / 40%);
    --theme-shadow: hsl(255 53% 10% / 25%);
  }

  @media (--HDcolor) {
    @supports (color: color(display-p3 0 0 0)) {
      --theme: color(display-p3 .4 0 .9);
    }
  }
}

হালকা এবং গাঢ় থিম ব্যাকগ্রাউন্ড কনিক ব্যাকগ্রাউন্ড

হালকা থিমে একটি প্রাণবন্ত সায়ান থেকে deeppink cyan কনিক গ্রেডিয়েন্ট রয়েছে যখন গাঢ় থিমে একটি গাঢ় সূক্ষ্ম কনিক গ্রেডিয়েন্ট রয়েছে। কনিক গ্রেডিয়েন্ট দিয়ে কী করা যায় সে সম্পর্কে আরও জানতে, conic.style দেখুন।

html {
  background: conic-gradient(at -10% 50%, deeppink, cyan);

  @media (--dark) {
    background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
  }
}
হালকা এবং গাঢ় রঙের পছন্দগুলির মধ্যে পটভূমি পরিবর্তনের প্রদর্শনী।

3D দৃষ্টিকোণ সক্ষম করা হচ্ছে

একটি ওয়েব পৃষ্ঠার 3D স্পেসে উপাদানগুলির অস্তিত্বের জন্য, দৃষ্টিকোণ সহ একটি ভিউপোর্ট শুরু করা প্রয়োজন৷ আমি body উপাদানের উপর দৃষ্টিভঙ্গি রাখা বেছে নিয়েছি এবং আমার পছন্দের শৈলী তৈরি করতে ভিউপোর্ট ইউনিট ব্যবহার করেছি।

body {
  perspective: 40vw;
}

এই ধরনের প্রভাব দৃষ্টিকোণ থাকতে পারে.

<ul> বোতাম তালিকা স্টাইল করা

এই উপাদানটি একটি ইন্টারেক্টিভ এবং 3D ভাসমান কার্ডের পাশাপাশি সামগ্রিক বোতাম তালিকা ম্যাক্রো লেআউটের জন্য দায়ী। এখানে যে অর্জন করার একটি উপায়.

বোতাম গ্রুপ লেআউট

ফ্লেক্সবক্স কন্টেইনার লেআউট পরিচালনা করতে পারে। ফ্লেক্স flex-direction সহ সারি থেকে কলামে ফ্লেক্সের ডিফল্ট দিক পরিবর্তন করুন এবং align-items জন্য stretch থেকে start পরিবর্তন করে প্রতিটি আইটেম তার বিষয়বস্তুর আকার নিশ্চিত করুন।

.threeD-button-set {
  /* remove <ul> margins */
  margin: 0;

  /* vertical rag-right layout */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2.5vh;
}

এরপরে, কন্টেইনারটিকে একটি 3D স্পেস প্রসঙ্গ হিসাবে স্থাপন করুন এবং কার্ডটি সুস্পষ্ট ঘূর্ণনের বাইরে ঘুরতে না পারে তা নিশ্চিত করতে CSS clamp() ফাংশন সেট আপ করুন। লক্ষ্য করুন যে ক্ল্যাম্পের মধ্যম মান একটি কাস্টম বৈশিষ্ট্য, এই --x এবং --y মানগুলি পরে মাউসের ইন্টারঅ্যাকশনের মাধ্যমে জাভাস্ক্রিপ্ট থেকে সেট করা হবে।

.threeD-button-set {
  …

  /* create 3D space context */
  transform-style: preserve-3d;

  /* clamped menu rotation to not be too extreme */
  transform:
    rotateY(
      clamp(
        calc(var(--_max-rotateY) * -1),
        var(--y),
        var(--_max-rotateY)
      )
    )
    rotateX(
      clamp(
        calc(var(--_max-rotateX) * -1),
        var(--x),
        var(--_max-rotateX)
      )
    )
  ;
}

এরপরে, পরিদর্শনকারী ব্যবহারকারীর সাথে গতি ঠিক থাকলে, ব্রাউজারে একটি ইঙ্গিত যোগ করুন যে এই আইটেমের রূপান্তরটি will-change সাথে ক্রমাগত পরিবর্তিত হবে। অতিরিক্তভাবে, ট্রান্সফর্মে একটি transition সেট করে ইন্টারপোলেশন সক্ষম করুন। এই রূপান্তর ঘটবে যখন মাউস কার্ডের সাথে ইন্টারঅ্যাক্ট করে, ঘূর্ণন পরিবর্তনে মসৃণ রূপান্তর সক্ষম করে। অ্যানিমেশন একটি ধ্রুবক চলমান অ্যানিমেশন যা কার্ডের মধ্যে থাকা 3D স্থানটি প্রদর্শন করে, এমনকি যদি একটি মাউস উপাদানটির সাথে ইন্টারঅ্যাক্ট করতে না পারে বা না করে।

@media (--motionOK) {
  .threeD-button-set {
    /* browser hint so it can be prepared and optimized */
    will-change: transform;

    /* transition transform style changes and run an infinite animation */
    transition: transform .1s ease;
    animation: rotate-y 5s ease-in-out infinite;
  }
}

rotate-y অ্যানিমেশন শুধুমাত্র মধ্যবর্তী কীফ্রেমটিকে 50% এ সেট করে কারণ ব্রাউজারটি উপাদানটির ডিফল্ট শৈলীতে 0% এবং 100% ডিফল্ট করবে। এটি এমন অ্যানিমেশনগুলির জন্য সংক্ষিপ্ত বিবরণ যা বিকল্প, একই অবস্থানে শুরু এবং শেষ করতে হবে। এটি অসীম বিকল্প অ্যানিমেশনগুলিকে স্পষ্ট করার একটি দুর্দান্ত উপায়।

@keyframes rotate-y {
  50% {
    transform: rotateY(15deg) rotateX(-6deg);
  }
}

<li> উপাদান স্টাইল করা

প্রতিটি তালিকা আইটেম ( <li> ) বোতাম এবং এর সীমানা উপাদান ধারণ করে। display শৈলী পরিবর্তিত হয়েছে তাই আইটেম একটি ::marker দেখায় না। position শৈলী relative সেট করা হয়েছে যাতে আসন্ন বোতাম ছদ্ম-উপাদানগুলি বোতামটি ব্যবহার করা সম্পূর্ণ এলাকার মধ্যে নিজেদের অবস্থান করতে পারে।

.threeD-button-set > li {
  /* change display type from list-item */
  display: inline-flex;

  /* create context for button pseudos */
  position: relative;

  /* create 3D space context */
  transform-style: preserve-3d;
}

দৃষ্টিকোণ দেখানোর জন্য তালিকার স্ক্রিনশট 3D স্পেসে ঘোরানো হয়েছে, এবং প্রতিটি তালিকা আইটেমের আর একটি বুলেট নেই।

<button> উপাদান স্টাইল করা

স্টাইলিং বোতামগুলি কঠিন কাজ হতে পারে, এখানে অনেকগুলি স্টেট এবং ইন্টারঅ্যাকশনের ধরন রয়েছে। ছদ্ম-উপাদান, অ্যানিমেশন এবং মিথস্ক্রিয়া ভারসাম্যের কারণে এই বোতামগুলি দ্রুত জটিল হয়ে যায়।

প্রাথমিক <button> শৈলী

নীচে মৌলিক শৈলী রয়েছে যা অন্যান্য রাজ্যগুলিকে সমর্থন করবে।

.threeD-button-set button {
  /* strip out default button styles */
  appearance: none;
  outline: none;
  border: none;

  /* bring in brand styles via props */
  background-color: var(--_btn-bg);
  color: var(--_btn-text);
  text-shadow: 0 1px 1px var(--_btn-text-shadow);

  /* large text rounded corner and padded*/
  font-size: 5vmin;
  font-family: Audiowide;
  padding-block: .75ch;
  padding-inline: 2ch;
  border-radius: 5px 20px;
}

3D পরিপ্রেক্ষিতে বোতাম তালিকার স্ক্রিনশট, এবার স্টাইল করা বোতাম সহ।

বোতাম ছদ্ম-উপাদান

বোতামের সীমানা ঐতিহ্যগত সীমানা নয়, তারা সীমানা সহ পরম অবস্থানের ছদ্ম-উপাদান।

Chrome Devtools এলিমেন্টস প্যানেলের স্ক্রিনশট একটি বোতাম সহ ::before এবং ::after এলিমেন্ট আছে।

এই উপাদানগুলি 3D দৃষ্টিভঙ্গি প্রদর্শনের জন্য গুরুত্বপূর্ণ যা প্রতিষ্ঠিত হয়েছে৷ এই ছদ্ম-উপাদানগুলির মধ্যে একটি বোতাম থেকে দূরে ঠেলে দেওয়া হবে, এবং একটি ব্যবহারকারীর কাছাকাছি টানা হবে। উপরের এবং নীচের বোতামগুলিতে প্রভাবটি সবচেয়ে লক্ষণীয়।

.threeD-button button {
  …

  &::after,
  &::before {
    /* create empty element */
    content: '';
    opacity: .8;

    /* cover the parent (button) */
    position: absolute;
    inset: 0;

    /* style the element for border accents */
    border: 1px solid var(--theme);
    border-radius: 5px 20px;
  }

  /* exceptions for one of the pseudo elements */
  /* this will be pushed back (3x) and have a thicker border */
  &::before {
    border-width: 3px;

    /* in dark mode, it glows! */
    @media (--dark) {
      box-shadow:
        0 0 25px var(--theme),
        inset 0 0 25px var(--theme);
    }
  }
}

3D রূপান্তর শৈলী

transform-style নীচে preserve-3d সেট করা হয়েছে যাতে শিশুরা z অক্ষের উপর নিজেদেরকে স্থান দিতে পারে। transform --distance কাস্টম সম্পত্তিতে সেট করা হয়েছে, যা হোভার এবং ফোকাসে বাড়ানো হবে।

.threeD-button-set button {
  …

  transform: translateZ(var(--distance));
  transform-style: preserve-3d;

  &::after {
    /* pull forward in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3));
  }

  &::before {
    /* push back in Z space with a 3x multiplier */
    transform: translateZ(calc(var(--distance) / 3 * -1));
  }
}

শর্তাধীন অ্যানিমেশন শৈলী

যদি ব্যবহারকারী গতির সাথে ঠিক থাকে, বোতামটি ব্রাউজারকে ইঙ্গিত দেয় যে ট্রান্সফর্ম বৈশিষ্ট্য পরিবর্তনের জন্য প্রস্তুত হওয়া উচিত এবং transform এবং background-color বৈশিষ্ট্যগুলির জন্য একটি ট্রানজিশন সেট করা হয়েছে। সময়কালের পার্থক্য লক্ষ্য করুন, আমি অনুভব করেছি এটি একটি সুন্দর সূক্ষ্ম স্তব্ধ প্রভাবের জন্য তৈরি।

.threeD-button-set button {
  …

  @media (--motionOK) {
    will-change: transform;
    transition:
      transform .2s ease,
      background-color .5s ease
    ;

    &::before,
    &::after {
      transition: transform .1s ease-out;
    }

    &::after    { transition-duration: .5s }
    &::before { transition-duration: .3s }
  }
}

হোভার এবং ফোকাস মিথস্ক্রিয়া শৈলী

মিথস্ক্রিয়া অ্যানিমেশনের লক্ষ্য হল ফ্ল্যাট প্রদর্শিত বোতামটি তৈরি করা স্তরগুলি ছড়িয়ে দেওয়া। --distance ভেরিয়েবল সেট করে এটি সম্পন্ন করুন, প্রাথমিকভাবে 1px এ। নিম্নলিখিত কোডের উদাহরণে দেখানো নির্বাচকটি পরীক্ষা করে যে বোতামটি একটি ডিভাইস দ্বারা ঘোরানো বা ফোকাস করা হচ্ছে কিনা যা একটি ফোকাস সূচক দেখতে পাবে এবং সক্রিয় হচ্ছে না। যদি তাই হয় তবে এটি নিম্নলিখিতগুলি করতে CSS প্রযোজ্য:

  • হোভার ব্যাকগ্রাউন্ডের রঙ প্রয়োগ করুন।
  • দূরত্ব বাড়ান।
  • একটি বাউন্স সহজ প্রভাব যোগ করুন.
  • ছদ্ম-উপাদান রূপান্তর স্তব্ধ.
.threeD-button-set button {
  …

  &:is(:hover, :focus-visible):not(:active) {
    /* subtle distance plus bg color change on hover/focus */
    --distance: 15px;
    background-color: var(--_btn-bg-hover);

    /* if motion is OK, setup transitions and increase distance */
    @media (--motionOK) {
      --distance: 3vmax;

      transition-timing-function: var(--_bounce-ease);
      transition-duration: .4s;

      &::after  { transition-duration: .5s }
      &::before { transition-duration: .3s }
    }
  }
}

3D দৃষ্টিকোণ reduced গতি পছন্দ জন্য এখনও সত্যিই ঝরঝরে ছিল. উপরের এবং নীচের উপাদানগুলি একটি সুন্দর সূক্ষ্ম উপায়ে প্রভাব দেখায়।

জাভাস্ক্রিপ্টের সাথে ছোট বর্ধন

ইন্টারফেসটি কীবোর্ড, স্ক্রিন রিডার, গেমপ্যাড, টাচ এবং একটি মাউস ইতিমধ্যেই ব্যবহারযোগ্য, তবে আমরা কয়েকটি পরিস্থিতি সহজ করতে জাভাস্ক্রিপ্টের কিছু হালকা স্পর্শ যুক্ত করতে পারি।

সাপোর্টিং তীর কী

ট্যাব কী মেনুতে নেভিগেট করার একটি সূক্ষ্ম উপায় কিন্তু আমি আশা করি যে দিকনির্দেশক প্যাড বা জয়স্টিকগুলি একটি গেমপ্যাডে ফোকাস নিয়ে যাবে৷ GUI চ্যালেঞ্জ ইন্টারফেসের জন্য প্রায়ই ব্যবহৃত roving-ux লাইব্রেরি আমাদের জন্য তীর কীগুলি পরিচালনা করবে। নীচের কোডটি লাইব্রেরীকে .threeD-button-set এর মধ্যে ফোকাস আটকাতে এবং বোতাম শিশুদের কাছে ফোকাস ফরোয়ার্ড করতে বলে।

import {rovingIndex} from 'roving-ux'

rovingIndex({
  element: document.querySelector('.threeD-button-set'),
  target: 'button',
})

মাউস প্যারালাক্স মিথস্ক্রিয়া

মাউসকে ট্র্যাক করা এবং মেনুটিকে কাত করার উদ্দেশ্য হল AR এবং VR ভিডিও গেম ইন্টারফেস অনুকরণ করা, যেখানে মাউসের পরিবর্তে আপনার কাছে একটি ভার্চুয়াল পয়েন্টার থাকতে পারে। উপাদানগুলি পয়েন্টার সম্পর্কে হাইপার সচেতন হলে এটি মজাদার হতে পারে।

যেহেতু এটি একটি ছোট অতিরিক্ত বৈশিষ্ট্য, তাই আমরা ব্যবহারকারীর গতি পছন্দের একটি প্রশ্নের পিছনে মিথস্ক্রিয়া রাখব। এছাড়াও, সেটআপের অংশ হিসাবে, querySelector এর সাথে মেমরিতে বোতাম তালিকা উপাদান সংরক্ষণ করুন এবং উপাদানটির সীমা menuRect ক্যাশে করুন। মাউস অবস্থানের উপর ভিত্তি করে কার্ডে প্রয়োগ করা ঘূর্ণন অফসেট নির্ধারণ করতে এই সীমাগুলি ব্যবহার করুন।

const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

এর পরে, আমাদের একটি ফাংশন দরকার যা মাউসের x এবং y অবস্থান গ্রহণ করে এবং একটি মান ফেরত দেয় যা আমরা কার্ডটি ঘোরাতে ব্যবহার করতে পারি। নিচের ফাংশনটি মাউসের অবস্থান ব্যবহার করে নির্ধারণ করে যে বাক্সের কোন দিকটি এটির ভিতরে আছে এবং কতটুকু। ডেল্টা ফাংশন থেকে ফিরে আসে।

const getAngles = (clientX, clientY) => {
  const { x, y, width, height } = menuRect

  const dx = clientX - (x + 0.5 * width)
  const dy = clientY - (y + 0.5 * height)

  return {dx,dy}
}

সবশেষে, মাউসের নড়াচড়া দেখুন, আমাদের getAngles() ফাংশনে অবস্থানটি পাস করুন এবং কাস্টম সম্পত্তি শৈলী হিসাবে ডেল্টা মান ব্যবহার করুন। আমি ডেল্টাকে প্যাড করার জন্য 20 দ্বারা ভাগ করেছি এবং এটিকে কম টুইচি করতে, এটি করার আরও ভাল উপায় হতে পারে। আপনি যদি শুরু থেকে মনে রাখেন, আমরা --x এবং --y প্রপগুলিকে একটি clamp() ফাংশনের মাঝখানে রাখি, এটি মাউসের অবস্থানটিকে কার্ডটিকে অযৌক্তিক অবস্থানে ঘোরাতে বাধা দেয়।

if (motionOK) {
  window.addEventListener('mousemove', ({target, clientX, clientY}) => {
    const {dx,dy} = getAngles(clientX, clientY)

    menu.attributeStyleMap.set('--x', `${dy / 20}deg`)
    menu.attributeStyleMap.set('--y', `${dx / 20}deg`)
  })
}

অনুবাদ এবং নির্দেশাবলী

অন্যান্য লেখার মোড এবং ভাষায় গেম মেনু পরীক্ষা করার সময় একটি গোট ছিল।

ব্যবহারকারী এজেন্ট স্টাইলশীটে writing-mode জন্য <button> উপাদানগুলির একটি !important শৈলী রয়েছে। এর মানে হল, পছন্দসই ডিজাইনের জন্য গেম মেনু HTML পরিবর্তন করতে হবে। লিঙ্কের তালিকায় বোতামের তালিকা পরিবর্তন করা যৌক্তিক বৈশিষ্ট্যগুলিকে মেনুর দিক পরিবর্তন করতে সক্ষম করে, কারণ <a> উপাদানগুলির একটি ব্রাউজার সরবরাহ করা হয় না !important শৈলী৷

উপসংহার

এখন যেহেতু আপনি জানেন যে আমি কীভাবে এটি করেছি, আপনি কীভাবে করবেন‽ 🙂 আপনি কি মেনুতে অ্যাক্সিলোমিটার ইন্টারঅ্যাকশন যোগ করতে পারেন, তাই আপনার ফোন টাইল করা মেনুতে ঘোরে? আমরা কোন গতির অভিজ্ঞতা উন্নত করতে পারি?

আসুন আমাদের পদ্ধতির বৈচিত্র্য আনুন এবং ওয়েবে তৈরি করার সমস্ত উপায় শিখি। একটি ডেমো তৈরি করুন, আমাকে লিঙ্কগুলি টুইট করুন এবং আমি নীচের সম্প্রদায়ের রিমিক্স বিভাগে এটি যুক্ত করব!

কমিউনিটি রিমিক্স

এখানে এখনো দেখার কিছু নেই!