DEV Community

Cover image for Comfort Table — طعم يشبه المنزل 🍲 | Perfect Landing
جميل الحطامي
جميل الحطامي

Posted on • Originally published at dancing-paletas-8f7e29.netlify.app

Comfort Table — طعم يشبه المنزل 🍲 | Perfect Landing

Frontend Challenge Perfect Landing Submission 🍲🥧

  •    <!DOCTYPE html> Comfort Table — طعم يشبه المنزل 🍲
    *, *::before, *::after { box-sizing: border-box; }
    
    :root {
      --cream: #fdf8f0;
      --warm-white: #faf3e8;
      --white: #ffffff;
      --brown-dark: #3d2a1e;
      --brown-medium: #6b4c3b;
      --brown-light: #a8877a;
      --gold: #d4a373;
      --gold-light: #f0d5b0;
      --orange-warm: #e07c4a;
      --border: rgba(107, 76, 59, .12);
      --shadow: 0 8px 30px rgba(61, 42, 30, .12);
      --shadow-hover: 0 12px 40px rgba(61, 42, 30, .20);
      --radius: 20px;
      --radius-sm: 12px;
      --transition: .3s cubic-bezier(.4,0,.2,1);
      --font-serif: Georgia, "Times New Roman", serif;
      --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Tahoma, Arial, sans-serif;
    }
    
    html { scroll-behavior: smooth; }
    body {
      margin: 0;
      font-family: var(--font-sans);
      background: var(--cream);
      color: var(--brown-dark);
      line-height: 1.6;
      overflow-x: hidden;
    }
    body.modal-open { overflow: hidden; }
    
    button, a { font: inherit; }
    button { -webkit-tap-highlight-color: transparent; }
    a { color: inherit; }
    img { max-width: 100%; display: block; }
    
    .container {
      width: min(1200px, calc(100% - 48px));
      margin-inline: auto;
    }
    
    .sr-only {
      position: absolute !important;
      width: 1px !important;
      height: 1px !important;
      padding: 0 !important;
      margin: -1px !important;
      overflow: hidden !important;
      clip: rect(0,0,0,0) !important;
      white-space: nowrap !important;
      border: 0 !important;
    }
    
    .skip-link {
      position: fixed;
      top: -100px;
      right: 50%;
      transform: translateX(50%);
      z-index: 10000;
      background: var(--brown-dark);
      color: var(--cream);
      padding: 12px 24px;
      border-radius: 0 0 var(--radius-sm) var(--radius-sm);
      text-decoration: none;
      font-weight: 700;
    }
    .skip-link:focus { top: 0; }
    
    h1, h2, h3 {
      font-family: var(--font-serif);
      line-height: 1.2;
      letter-spacing: -.02em;
      margin-top: 0;
    }
    h1 { font-size: clamp(2.6rem, 8vw, 5rem); margin-bottom: 0; }
    h2 { font-size: clamp(2rem, 5vw, 3.2rem); margin-bottom: .5rem; }
    h3 { font-size: clamp(1.4rem, 3vw, 2rem); }
    
    .highlight { color: var(--orange-warm); }
    
    .btn {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      min-height: 48px;
      padding: 14px 30px;
      border: 0;
      border-radius: 999px;
      cursor: pointer;
      text-decoration: none;
      font-weight: 700;
      transition: transform var(--transition), background var(--transition), color var(--transition), box-shadow var(--transition);
    }
    .btn:hover { transform: translateY(-2px); }
    .btn-primary {
      background: var(--brown-dark);
      color: var(--cream);
      box-shadow: var(--shadow);
    }
    .btn-primary:hover, .btn-primary:focus-visible {
      background: var(--orange-warm);
      box-shadow: var(--shadow-hover);
    }
    .btn-secondary { background: var(--gold); color: var(--brown-dark); }
    .btn-secondary:hover, .btn-secondary:focus-visible { background: var(--gold-light); }
    .btn-ghost {
      background: transparent;
      color: var(--brown-dark);
      border: 2px solid var(--brown-light);
    }
    .btn-ghost:hover, .btn-ghost:focus-visible {
      background: var(--brown-dark);
      color: var(--cream);
      border-color: var(--brown-dark);
    }
    
    :focus-visible {
      outline: 3px solid var(--orange-warm);
      outline-offset: 3px;
    }
    
    /* HEADER */
    header {
      position: sticky;
      top: 0;
      z-index: 1000;
      padding: 18px 0;
      background: rgba(253,248,240,.94);
      backdrop-filter: blur(10px);
      border-bottom: 1px solid var(--border);
    }
    .nav-container {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 20px;
    }
    .logo {
      display: inline-flex;
      align-items: center;
      gap: 10px;
      font: 700 1.4rem var(--font-serif);
      text-decoration: none;
      white-space: nowrap;
    }
    .logo-icon { font-size: 1.8rem; }
    .logo-highlight { color: var(--orange-warm); }
    
    nav { position: relative; }
    nav ul {
      display: flex;
      align-items: center;
      gap: 30px;
      list-style: none;
      padding: 0;
      margin: 0;
    }
    nav a {
      position: relative;
      text-decoration: none;
      font-weight: 600;
      color: var(--brown-dark);
      padding: 6px 0;
    }
    nav a::after {
      content: "";
      position: absolute;
      right: 0;
      bottom: 0;
      width: 0;
      height: 2px;
      background: var(--orange-warm);
      transition: width var(--transition);
    }
    nav a:hover::after, nav a:focus-visible::after,
    nav a[aria-current="page"]::after { width: 100%; }
    nav a[aria-current="page"] { color: var(--orange-warm); }
    
    .nav-toggle {
      display: none;
      border: 0;
      background: transparent;
      padding: 10px;
      cursor: pointer;
      border-radius: 10px;
    }
    .hamburger, .hamburger::before, .hamburger::after {
      display: block;
      width: 28px;
      height: 3px;
      border-radius: 3px;
      background: var(--brown-dark);
    }
    .hamburger { position: relative; }
    .hamburger::before, .hamburger::after {
      content: "";
      position: absolute;
      right: 0;
    }
    .hamburger::before { top: -8px; }
    .hamburger::after { bottom: -8px; }
    
    /* HERO */
    .hero {
      padding: 70px 0 90px;
      background: linear-gradient(180deg, var(--cream), var(--warm-white));
    }
    .hero-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      align-items: center;
      gap: 60px;
    }
    .hero-content { text-align: right; }
    .hero-tag {
      margin: 0 0 12px;
      color: var(--brown-light);
      font-size: .9rem;
      font-weight: 700;
      letter-spacing: 2px;
      text-transform: uppercase;
    }
    .hero-sub {
      margin: 20px 0 32px;
      color: var(--brown-medium);
      font-size: 1.25rem;
      line-height: 1.7;
    }
    .hero-visual {
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 330px;
    }
    
    /* CSS ART */
    .comfort-plate {
      position: relative;
      width: 300px;
      height: 300px;
    }
    .plate {
      position: absolute;
      inset: 20px;
      border-radius: 50%;
      background: radial-gradient(ellipse at 30% 30%, #f5ede4, #dccfc4);
      box-shadow: inset 0 -20px 40px rgba(61,42,30,.15), 0 20px 60px rgba(61,42,30,.2);
    }
    .plate::after {
      content: "";
      position: absolute;
      width: 68%;
      height: 68%;
      left: 16%;
      top: 16%;
      border-radius: 50%;
      background: radial-gradient(ellipse at 40% 40%, #f0d5b0, #d4a373);
      box-shadow: inset 0 -10px 20px rgba(61,42,30,.1);
    }
    .steam {
      position: absolute;
      width: 12px;
      height: 42px;
      border-radius: 50%;
      background: rgba(255,255,255,.34);
      filter: blur(4px);
      animation: steam-rise 3s ease-in-out infinite;
    }
    .steam-1 { left: 37%; top: 10%; }
    .steam-2 { left: 50%; top: 3%; height: 52px; animation-delay: 1s; }
    .steam-3 { left: 63%; top: 10%; animation-delay: 2s; }
    @keyframes steam-rise {
      0% { transform: translateY(0) scale(1); opacity: .6; }
      50% { transform: translateY(-30px) scale(1.4); opacity: .2; }
      100% { transform: translateY(-60px) scale(.8); opacity: 0; }
    }
    
    /* FINDER */
    .finder {
      padding: 90px 0;
      background: var(--warm-white);
    }
    .finder > .container > h2,
    .finder-sub { text-align: center; }
    .finder-sub {
      color: var(--brown-medium);
      font-size: 1.1rem;
      margin: 0 0 48px;
    }
    .finder-step {
      border: 0;
      padding: 0;
      margin: 0 0 34px;
    }
    .finder-step-label {
      display: block;
      text-align: center;
      margin-bottom: 20px;
      color: var(--brown-dark);
      font-size: 1.2rem;
      font-weight: 700;
    }
    .mood-grid {
      display: grid;
      grid-template-columns: repeat(5, minmax(0, 1fr));
      gap: 18px;
      max-width: 1000px;
      margin: auto;
    }
    .mood-card, .pref-card, .time-card {
      border: 2px solid transparent;
      background: var(--white);
      color: var(--brown-dark);
      cursor: pointer;
      box-shadow: var(--shadow);
      transition: transform var(--transition), border-color var(--transition), background var(--transition), color var(--transition), box-shadow var(--transition);
    }
    .mood-card {
      min-height: 175px;
      padding: 24px 16px;
      border-radius: var(--radius);
      text-align: center;
    }
    .mood-card:hover, .pref-card:hover, .time-card:hover {
      transform: translateY(-3px);
      border-color: var(--gold);
      box-shadow: var(--shadow-hover);
    }
    .mood-card.selected, .pref-card.selected, .time-card.selected {
      background: var(--brown-dark);
      color: var(--cream);
      border-color: var(--brown-dark);
    }
    .mood-icon {
      display: block;
      font-size: 2.7rem;
      margin-bottom: 8px;
    }
    .mood-label { display: block; font-weight: 800; font-size: 1.05rem; }
    .mood-desc {
      display: block;
      margin-top: 5px;
      color: var(--brown-light);
      font-size: .84rem;
    }
    .mood-card.selected .mood-desc { color: rgba(255,255,255,.72); }
    
    .pref-grid, .time-grid {
      display: grid;
      grid-template-columns: repeat(6, minmax(0, 1fr));
      gap: 14px;
      max-width: 850px;
      margin: auto;
    }
    .pref-card, .time-card {
      padding: 16px 12px;
      border-radius: var(--radius-sm);
      font-weight: 700;
      min-height: 54px;
    }
    
    /* RECOMMENDATION */
    .recommendation {
      max-width: 760px;
      margin: 48px auto 0;
      animation: fade-up .5s ease;
    }
    @keyframes fade-up {
      from { opacity: 0; transform: translateY(25px); }
      to { opacity: 1; transform: translateY(0); }
    }
    .rec-card {
      background: var(--white);
      border-radius: var(--radius);
      padding: 38px;
      box-shadow: var(--shadow-hover);
      display: grid;
      grid-template-columns: 130px 1fr;
      gap: 30px;
      align-items: center;
    }
    .rec-visual {
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .rec-emoji { font-size: 5rem; line-height: 1; }
    .rec-label {
      margin: 0;
      color: var(--brown-light);
      font-size: .8rem;
      font-weight: 700;
      letter-spacing: 2px;
      text-transform: uppercase;
    }
    .rec-content h3 { margin: 6px 0; }
    .rec-desc { color: var(--brown-medium); margin: 8px 0 16px; }
    .rec-meta {
      display: flex;
      flex-wrap: wrap;
      gap: 10px 16px;
      color: var(--brown-light);
      font-size: .9rem;
      margin-bottom: 20px;
    }
    .rec-actions { display: flex; flex-wrap: wrap; gap: 12px; }
    
    /* MODAL */
    .modal[hidden], .recommendation[hidden], .finder-step[hidden] { display: none !important; }
    .modal {
      position: fixed;
      inset: 0;
      z-index: 9999;
      display: grid;
      place-items: center;
      padding: 24px;
    }
    .modal-overlay {
      position: absolute;
      inset: 0;
      background: rgba(61,42,30,.65);
      backdrop-filter: blur(4px);
    }
    .modal-content {
      position: relative;
      z-index: 1;
      width: min(600px, 100%);
      max-height: 90vh;
      overflow-y: auto;
      padding: 40px;
      border-radius: var(--radius);
      background: var(--white);
      box-shadow: 0 40px 80px rgba(0,0,0,.3);
      animation: fade-up .3s ease;
    }
    .modal-close {
      position: absolute;
      top: 14px;
      left: 14px;
      width: 42px;
      height: 42px;
      border: 0;
      border-radius: 50%;
      background: transparent;
      color: var(--brown-light);
      cursor: pointer;
      font-size: 1.4rem;
    }
    .modal-close:hover { background: var(--cream); color: var(--brown-dark); }
    .modal-dish-emoji { text-align: center; font-size: 4rem; margin-bottom: 8px; }
    .modal-body h3 { margin-bottom: 8px; }
    .modal-description { color: var(--brown-medium); margin-bottom: 18px; }
    .dish-detail { margin: 14px 0; }
    .dish-detail strong { color: var(--brown-medium); }
    .modal-body ul { list-style: none; padding: 0; margin: 8px 0 0; }
    .modal-body li { padding: 6px 0; border-bottom: 1px solid var(--border); }
    
    /* MENU */
    .menu {
      padding: 90px 0;
      background: var(--cream);
    }
    .menu > .container > h2,
    .menu-sub { text-align: center; }
    .menu-sub { color: var(--brown-medium); margin: 0 0 40px; }
    .filter-bar {
      display: flex;
      justify-content: center;
      flex-wrap: wrap;
      gap: 10px;
      margin-bottom: 40px;
    }
    .filter-btn {
      padding: 10px 22px;
      border: 2px solid transparent;
      border-radius: 999px;
      background: var(--white);
      color: var(--brown-dark);
      cursor: pointer;
      font-size: .9rem;
      font-weight: 700;
      transition: var(--transition);
    }
    .filter-btn:hover { border-color: var(--gold); }
    .filter-btn.active, .filter-btn[aria-pressed="true"] {
      background: var(--brown-dark);
      color: var(--cream);
      border-color: var(--brown-dark);
    }
    
    .dish-grid {
      display: grid;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      gap: 22px;
    }
    .dish-card {
      background: var(--white);
      border-radius: var(--radius);
      padding: 24px;
      text-align: center;
      box-shadow: var(--shadow);
      transition: var(--transition);
    }
    .dish-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-hover); }
    .dish-emoji { display: block; font-size: 3.5rem; line-height: 1; margin-bottom: 10px; }
    .dish-name { font-weight: 800; font-size: 1.08rem; }
    .dish-category {
      margin-top: 3px;
      color: var(--brown-light);
      font-size: .78rem;
      font-weight: 700;
    }
    .dish-tags {
      display: flex;
      justify-content: center;
      flex-wrap: wrap;
      gap: 6px;
      margin: 10px 0 14px;
    }
    .dish-tag {
      padding: 3px 10px;
      border-radius: 999px;
      background: var(--cream);
      color: var(--brown-medium);
      font-size: .7rem;
    }
    .dish-card .btn {
      width: 100%;
      min-height: 42px;
      padding: 9px 16px;
      font-size: .86rem;
    }
    
    /* STORY / CTA / FOOTER */
    .story {
      padding: 100px 0;
      background: var(--brown-dark);
      color: var(--cream);
    }
    .story-content { max-width: 760px; margin: auto; text-align: center; }
    .story-tag {
      color: var(--gold);
      font-size: .8rem;
      font-weight: 700;
      letter-spacing: 3px;
      text-transform: uppercase;
    }
    .story .highlight { color: var(--gold); }
    .story-text {
      color: rgba(255,255,255,.8);
      font-size: 1.12rem;
      line-height: 1.9;
      margin-top: 20px;
    }
    .cta {
      padding: 85px 0;
      text-align: center;
      background: linear-gradient(180deg, var(--cream), var(--warm-white));
    }
    .cta-sub { color: var(--brown-medium); font-size: 1.15rem; margin: 0 0 30px; }
    
    footer {
      padding: 60px 0 28px;
      background: var(--brown-dark);
      color: rgba(255,255,255,.72);
      border-top: 2px solid rgba(255,255,255,.05);
    }
    .footer-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 40px;
      margin-bottom: 40px;
    }
    .footer-brand .logo { color: var(--cream); }
    .footer-tagline { color: var(--gold-light); font-style: italic; margin: 8px 0 0; }
    .footer-desc { font-size: .9rem; opacity: .7; margin: 4px 0 0; }
    .footer-credit { text-align: left; line-height: 1.8; }
    .footer-credit strong { color: var(--cream); }
    .footer-credit a { color: var(--gold); text-decoration: none; }
    .footer-credit a:hover, .footer-credit a:focus-visible { color: var(--gold-light); text-decoration: underline; }
    .footer-bottom {
      border-top: 1px solid rgba(255,255,255,.08);
      padding-top: 24px;
      text-align: center;
      font-size: .85rem;
      opacity: .55;
    }
    
    /* RESPONSIVE */
    @media (max-width: 1050px) {
      .mood-grid { grid-template-columns: repeat(3, minmax(0,1fr)); }
      .dish-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
      .hero-grid { grid-template-columns: 1fr; text-align: center; }
      .hero-content { text-align: center; }
      .hero-visual { order: -1; }
    }
    
    @media (max-width: 768px) {
      .container { width: min(100% - 32px, 1200px); }
      .nav-toggle { display: block; }
      nav ul {
        display: none;
        position: absolute;
        top: calc(100% + 12px);
        left: 0;
        min-width: 210px;
        padding: 18px;
        flex-direction: column;
        align-items: stretch;
        gap: 12px;
        background: var(--white);
        border-radius: var(--radius-sm);
        box-shadow: var(--shadow-hover);
      }
      nav ul.open { display: flex; }
      .mood-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
      .pref-grid, .time-grid { grid-template-columns: repeat(3, minmax(0,1fr)); }
      .rec-card { grid-template-columns: 1fr; text-align: center; }
      .rec-actions { justify-content: center; }
      .footer-grid { grid-template-columns: 1fr; text-align: center; }
      .footer-credit { text-align: center; }
    }
    
    @media (max-width: 480px) {
      .hero { padding: 45px 0 65px; }
      .comfort-plate { width: 220px; height: 220px; }
      .plate { inset: 15px; }
      .mood-grid { grid-template-columns: 1fr; }
      .pref-grid, .time-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
      .dish-grid { grid-template-columns: 1fr; }
      .rec-card, .modal-content { padding: 24px; }
      .btn { width: 100%; }
      .rec-actions { flex-direction: column; }
      .modal-content { margin: 8px; }
    }
    
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
      }
      .steam { display: none; }
      .recommendation, .modal-content { animation: none; }
    }
    


    تخطي إلى المحتوى الرئيسي


    🍲
    Comfort Table
      <nav aria-label="القائمة الرئيسية">
        <button class="nav-toggle" id="nav-toggle" type="button"
                aria-expanded="false" aria-controls="nav-menu">
          <span class="sr-only">فتح القائمة</span>
          <span class="hamburger" aria-hidden="true"></span>
        </button>
        <ul id="nav-menu">
          <li><a href="#finder" aria-current="page">ابحث عن الراحة</a></li>
          <li><a href="#menu">القائمة</a></li>
          <li><a href="#story">القصة</a></li>
        </ul>
      </nav>
    </div>
    

    🍲 Comfort Table

    طعم يشبه
    المنزل

    أخبرنا بمشاعرك.
    سنجد لك طعامك المريح.

    ابحث عن راحتك ←
        <div class="hero-visual" aria-hidden="true">
          <div class="comfort-plate">
            <div class="plate"></div>
            <div class="steam steam-1"></div>
            <div class="steam steam-2"></div>
            <div class="steam steam-3"></div>
          </div>
        </div>
      </div>
    </section>
    
    <section class="finder" id="finder" aria-labelledby="finder-title">
      <div class="container">
        <h2 id="finder-title">ما نوع الراحة التي تبحث عنها؟</h2>
        <p class="finder-sub">اختر شعورك، وسنوصي بالطبق المناسب لك.</p>
    
        <div id="rec-announcer" class="sr-only" aria-live="polite" aria-atomic="true"></div>
    
        <fieldset class="finder-step">
          <legend class="sr-only">الخطوة 1: اختر شعورك</legend>
          <div class="mood-grid" id="mood-grid">
            <button class="mood-card" data-mood="home" type="button"
                    aria-pressed="false">
              <span class="mood-icon" aria-hidden="true">🏠</span>
              <span class="mood-label">المنزل</span>
              <span class="mood-desc">أشتاق إلى طعم المنزل.</span>
            </button>
    
            <button class="mood-card" data-mood="nostalgia" type="button"
                    aria-pressed="false">
              <span class="mood-icon" aria-hidden="true">❤️</span>
              <span class="mood-label">الحنين</span>
              <span class="mood-desc">أريد شيئًا يعيد الذكريات.</span>
            </button>
    
            <button class="mood-card" data-mood="cozy" type="button"
                    aria-pressed="false">
              <span class="mood-icon" aria-hidden="true">😌</span>
              <span class="mood-label">الدفء</span>
              <span class="mood-desc">أريد شيئًا دافئًا ومريحًا.</span>
            </button>
    
            <button class="mood-card" data-mood="hearty" type="button"
                    aria-pressed="false">
              <span class="mood-icon" aria-hidden="true">🔥</span>
              <span class="mood-label">المشبع</span>
              <span class="mood-desc">أريد وجبة تشبعني.</span>
            </button>
    
            <button class="mood-card" data-mood="quick" type="button"
                    aria-pressed="false">
              <span class="mood-icon" aria-hidden="true">⚡</span>
              <span class="mood-label">السريع</span>
              <span class="mood-desc">راحة سريعة.</span>
            </button>
          </div>
        </fieldset>
    
        <fieldset class="finder-step" id="pref-step" hidden>
          <legend class="finder-step-label">ما الذي تفضله؟</legend>
          <div class="pref-grid" id="pref-grid">
            <button class="pref-card" data-pref="rice" type="button" aria-pressed="false">🍚 أرز</button>
            <button class="pref-card" data-pref="meat" type="button" aria-pressed="false">🥩 لحم</button>
            <button class="pref-card" data-pref="chicken" type="button" aria-pressed="false">🍗 دجاج</button>
            <button class="pref-card" data-pref="veg" type="button" aria-pressed="false">🥗 نباتي</button>
            <button class="pref-card" data-pref="sweet" type="button" aria-pressed="false">🍯 حلو</button>
            <button class="pref-card" data-pref="any" type="button" aria-pressed="false">✨ أي شيء</button>
          </div>
        </fieldset>
    
        <fieldset class="finder-step" id="time-step" hidden>
          <legend class="finder-step-label">كم لديك من الوقت؟</legend>
          <div class="time-grid" id="time-grid">
            <button class="time-card" data-time="10" type="button" aria-pressed="false">⏱ 10 دقائق</button>
            <button class="time-card" data-time="30" type="button" aria-pressed="false">⏱ 30 دقيقة</button>
            <button class="time-card" data-time="any" type="button" aria-pressed="false">⏱ لا يهم</button>
          </div>
        </fieldset>
    
        <div class="recommendation" id="recommendation" hidden>
          <div class="rec-card" id="rec-card" tabindex="-1">
            <div class="rec-visual" aria-hidden="true">
              <span class="rec-emoji" id="rec-emoji">🥘</span>
            </div>
            <div class="rec-content">
              <p class="rec-label">❤️ وجبتك المثالية</p>
              <h3 id="rec-name">سلتة يمنية</h3>
              <p class="rec-desc" id="rec-desc"></p>
              <div class="rec-meta" id="rec-meta"></div>
              <div class="rec-actions">
                <button class="btn btn-secondary" id="explore-dish" type="button">استكشف الطبق</button>
                <button class="btn btn-ghost" id="reset-finder" type="button">جرّب شعورًا آخر</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
    
    <div class="modal" id="dish-modal" hidden role="dialog"
         aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-description">
      <div class="modal-overlay" id="modal-overlay"></div>
      <div class="modal-content" role="document">
        <button class="modal-close" id="modal-close" type="button" aria-label="إغلاق النافذة">✕</button>
        <div class="modal-body" id="modal-body"></div>
      </div>
    </div>
    
    <section class="menu" id="menu" aria-labelledby="menu-title">
      <div class="container">
        <h2 id="menu-title">استكشف قائمة الراحة</h2>
        <p class="menu-sub">أطباق يمنية وعربية محضرة بدفء وعناية.</p>
    
        <div class="filter-bar" role="group" aria-label="تصفية الأطباق">
          <button class="filter-btn active" data-filter="all" aria-pressed="true" type="button">الكل</button>
          <button class="filter-btn" data-filter="bowl" aria-pressed="false" type="button">أطباق رئيسية</button>
          <button class="filter-btn" data-filter="bake" aria-pressed="false" type="button">مخبوزات</button>
          <button class="filter-btn" data-filter="sweet" aria-pressed="false" type="button">حلويات</button>
          <button class="filter-btn" data-filter="quick" aria-pressed="false" type="button">سريع (≤ 30 د)</button>
        </div>
    
        <div class="dish-grid" id="dish-grid" role="list" aria-live="polite"></div>
      </div>
    </section>
    
    <section class="story" id="story" aria-labelledby="story-title">
      <div class="container">
        <div class="story-content">
          <p class="story-tag">القصة</p>
          <h2 id="story-title">بعض الوجبات تُطعمك.<br><span class="highlight">وبعضها يتذكرك.</span></h2>
          <p class="story-text">الطعام المريح ليس مجرد مكونات. إنه اللحظات التي تحملها — مطبخ الجدة، بعد ظهر ممطر، وجبة تشاركها مع من تحب.</p>
          <p class="story-text">في Comfort Table، نؤمن أن كل طبق يحكي قصة. أخبرنا بمشاعرك، وسنجد لك الطبق الذي يتحدث إليك.</p>
        </div>
      </div>
    </section>
    
    <section class="cta" aria-labelledby="cta-title">
      <div class="container">
        <h2 id="cta-title">وجبتك المريحة التالية تنتظرك.</h2>
        <p class="cta-sub">ابحث عن الطبق الذي يشعرك وكأنك في المنزل.</p>
        <a class="btn btn-primary" href="#finder">ابحث عن راحتك ←</a>
      </div>
    </section>
    

    🍲
    Comfort Table

    طعم يشبه المنزل.

    مشروع واجهة وتجربة طعام رقمية.

        <div class="footer-credit">
          <p><strong>تصميم وتطوير<br>جميل الحطامي</strong></p>
          <p>شركة جميل الحطامي للتسويق والمبيعات للتسوق الإلكتروني</p>
          <p>تم بناء هذا المشروع لتحدي DEV Community — Comfort Food Edition.</p>
          <p>
            <a href="https://github.com/2gmil991-cloud" target="_blank" rel="noopener noreferrer">GitHub</a>
            ·
            <a href="https://dancing-paletas-8f7e29.netlify.app/" target="_blank" rel="noopener noreferrer">الموقع المنشور</a>
          </p>
        </div>
      </div>
    
      <div class="footer-bottom">
        <p>© 2026 Comfort Table. جميع الحقوق محفوظة.</p>
      </div>
    </div>
    

    <br> &quot;use strict&quot;;</p> <div class="highlight"><pre class="highlight plaintext"><code>/* ============================================================ DATA ============================================================ */ const dishes = [ { id: "saltah", name: "سلتة يمنية", emoji: "🥘", category: "bowl", moods: ["home", "hearty", "cozy"], preferences: ["meat", "chicken"], time: 30, description: "وجبة يمنية أصيلة تُقدم في إناء فخاري مع اللحم والخضروات والبهارات العطرية.", ingredients: ["لحم", "طماطم", "فلفل", "ثوم", "بهارات يمنية", "خبز ملوج"], tags: ["يمني", "مشبع", "دافئ"], comfortLevel: 5 }, { id: "mandi", name: "مندي", emoji: "🍚", category: "bowl", moods: ["home", "hearty", "nostalgia"], preferences: ["rice", "meat", "chicken"], time: 60, description: "أرز مندي بلحم الضأن أو الدجاج، مطبوخ على الطريقة اليمنية الأصيلة مع التوابل والزعفران.", ingredients: ["أرز بسمتي", "لحم ضأن أو دجاج", "بهارات", "زعفران", "تمر", "مكسرات"], tags: ["يمني", "مشبع", "تقليدي"], comfortLevel: 5 }, { id: "shorba", name: "شوربة دافئة", emoji: "🍲", category: "bowl", moods: ["cozy", "home", "quick"], preferences: ["veg", "chicken"], time: 20, description: "شوربة غنية بالخضروات والدجاج، تمنحك الدفء في كل ملعقة.", ingredients: ["دجاج", "خضروات مشكلة", "نودلز", "بهارات", "ليمون"], tags: ["دافئ", "مريح", "سريع"], comfortLevel: 4 }, { id: "kabsa", name: "كبسة", emoji: "🍛", category: "bowl", moods: ["home", "hearty"], preferences: ["rice", "meat", "chicken"], time: 45, description: "طبق خليجي شهير، أرز مطهو مع اللحم أو الدجاج والتوابل العطرية.", ingredients: ["أرز", "لحم أو دجاج", "بصل", "طماطم", "بهارات كبسة", "مكسرات"], tags: ["خليجي", "مشبع", "منزلي"], comfortLevel: 4 }, { id: "malooga", name: "ملوج بالعسل", emoji: "🫓", category: "bake", moods: ["nostalgia", "cozy"], preferences: ["sweet"], time: 30, description: "خبز يمني تقليدي مع عسل وزبدة؛ دفء العائلة في كل قضمة.", ingredients: ["طحين", "عسل", "زبدة", "خميرة", "هيل"], tags: ["يمني", "حلو", "تقليدي"], comfortLevel: 5 }, { id: "bint-al-sahn", name: "بنت الصحن", emoji: "🍯", category: "sweet", moods: ["nostalgia", "home", "cozy"], preferences: ["sweet"], time: 40, description: "حلوى يمنية شهيرة من طبقات العجين والعسل والزبدة، تقدم في المناسبات.", ingredients: ["طحين", "عسل", "زبدة", "زعفران", "هيل"], tags: ["يمني", "حلو", "احتفالي"], comfortLevel: 5 }, { id: "fahsa", name: "فحسة", emoji: "🥩", category: "bowl", moods: ["hearty", "home"], preferences: ["meat"], time: 50, description: "لحم مطهو مع التوابل اليمنية والفلفل الحار، يقدم مع خبز ملوج.", ingredients: ["لحم", "فلفل حار", "طماطم", "ثوم", "بهارات", "خبز"], tags: ["يمني", "مشبع", "دافئ"], comfortLevel: 5 }, { id: "zabadi", name: "زبادي بالعسل", emoji: "🥛", category: "sweet", moods: ["quick", "cozy"], preferences: ["sweet"], time: 5, description: "زبادي طازج مع عسل ومكسرات؛ راحة سريعة وبسيطة.", ingredients: ["زبادي", "عسل", "جوز", "فستق"], tags: ["سريع", "منعش", "خفيف"], comfortLevel: 3 } ]; const categoryNames = { bowl: "أطباق رئيسية", bake: "مخبوزات", sweet: "حلويات" }; const state = { mood: null, preference: null, time: null, currentDish: null, lastFocusedElement: null, activeFilter: "all" }; const $ = (selector, root = document) =&gt; root.querySelector(selector); const $$ = (selector, root = document) =&gt; [...root.querySelectorAll(selector)]; const moodGrid = $("#mood-grid"); const prefGrid = $("#pref-grid"); const timeGrid = $("#time-grid"); const prefStep = $("#pref-step"); const timeStep = $("#time-step"); const recommendation = $("#recommendation"); const recCard = $("#rec-card"); const recEmoji = $("#rec-emoji"); const recName = $("#rec-name"); const recDesc = $("#rec-desc"); const recMeta = $("#rec-meta"); const recAnnouncer = $("#rec-announcer"); const exploreBtn = $("#explore-dish"); const resetBtn = $("#reset-finder"); const dishGrid = $("#dish-grid"); const modal = $("#dish-modal"); const modalBody = $("#modal-body"); const modalClose = $("#modal-close"); const modalOverlay = $("#modal-overlay"); const navToggle = $("#nav-toggle"); const navMenu = $("#nav-menu"); const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; function scrollToElement(element, block = "center") { element.scrollIntoView({ behavior: prefersReducedMotion ? "auto" : "smooth", block }); } /* ============================================================ WEIGHTED SCORING — 40% + 30% + 20% + 10% = 100% Mood = 40 Preference = 30 Time = 20 Comfort = 10 ============================================================ */ function scoreTime(dishTime, selectedTime) { if (!selectedTime || selectedTime === "any") return 20; const maxTime = Number(selectedTime); if (dishTime &lt;= maxTime) return 20; const difference = dishTime - maxTime; if (difference &lt;= 15) return 10; return 0; } function calculateMatchScore(dish, mood, preference, time) { let score = 0; if (dish.moods.includes(mood)) score += 40; if (!preference || preference === "any") { score += 30; } else if (dish.preferences.includes(preference)) { score += 30; } score += scoreTime(dish.time, time); score += dish.comfortLevel * 2; return Math.min(100, Math.round(score)); } function getRecommendation(mood, preference, time) { if (!mood) return null; const scored = dishes.map(dish =&gt; ({ ...dish, matchScore: calculateMatchScore(dish, mood, preference, time) })); scored.sort((a, b) =&gt; { if (b.matchScore !== a.matchScore) return b.matchScore - a.matchScore; if (a.time !== b.time) return a.time - b.time; return b.comfortLevel - a.comfortLevel; }); return scored[0] || null; } function showRecommendation(dish) { state.currentDish = dish; recEmoji.textContent = dish.emoji; recName.textContent = dish.name; recDesc.textContent = dish.description; recMeta.innerHTML = [ `&lt;span&gt;⏱ ${dish.time} دقيقة&lt;/span&gt;`, `&lt;span&gt;🏷️ ${dish.tags.join(" · ")}&lt;/span&gt;`, `&lt;span&gt;⭐ تطابق ${dish.matchScore}%&lt;/span&gt;` ].join(""); recommendation.hidden = false; recAnnouncer.textContent = `تم العثور على طبقك المثالي: ${dish.name}، بنسبة تطابق ${dish.matchScore} بالمئة.`; scrollToElement(recommendation); window.setTimeout(() =&gt; { recCard.focus({ preventScroll: true }); }, prefersReducedMotion ? 0 : 250); } /* ============================================================ SELECTION HELPERS ============================================================ */ function setPressed(container, selector, selected) { $$(selector, container).forEach(button =&gt; { const active = button === selected; button.classList.toggle("selected", active); button.setAttribute("aria-pressed", String(active)); }); } function resetFinder() { state.mood = null; state.preference = null; state.time = null; state.currentDish = null; $$(".mood-card, .pref-card, .time-card").forEach(button =&gt; { button.classList.remove("selected"); button.setAttribute("aria-pressed", "false"); }); prefStep.hidden = true; timeStep.hidden = true; recommendation.hidden = true; recAnnouncer.textContent = "تمت إعادة تعيين البحث. اختر شعورك للبدء."; scrollToElement(moodGrid, "start"); window.setTimeout(() =&gt; { const firstMood = $(".mood-card", moodGrid); if (firstMood) firstMood.focus({ preventScroll: true }); }, prefersReducedMotion ? 0 : 250); } /* ============================================================ FINDER EVENTS ============================================================ */ moodGrid.addEventListener("click", event =&gt; { const button = event.target.closest(".mood-card"); if (!button) return; state.mood = button.dataset.mood; state.preference = null; state.time = null; state.currentDish = null; setPressed(moodGrid, ".mood-card", button); $$(".pref-card", prefGrid).forEach(item =&gt; { item.classList.remove("selected"); item.setAttribute("aria-pressed", "false"); }); $$(".time-card", timeGrid).forEach(item =&gt; { item.classList.remove("selected"); item.setAttribute("aria-pressed", "false"); }); prefStep.hidden = false; timeStep.hidden = true; recommendation.hidden = true; scrollToElement(prefStep); window.setTimeout(() =&gt; { const firstPreference = $(".pref-card", prefGrid); if (firstPreference) firstPreference.focus({ preventScroll: true }); }, prefersReducedMotion ? 0 : 250); }); prefGrid.addEventListener("click", event =&gt; { const button = event.target.closest(".pref-card"); if (!button) return; state.preference = button.dataset.pref; state.time = null; setPressed(prefGrid, ".pref-card", button); $$(".time-card", timeGrid).forEach(item =&gt; { item.classList.remove("selected"); item.setAttribute("aria-pressed", "false"); }); timeStep.hidden = false; recommendation.hidden = true; scrollToElement(timeStep); window.setTimeout(() =&gt; { const firstTime = $(".time-card", timeGrid); if (firstTime) firstTime.focus({ preventScroll: true }); }, prefersReducedMotion ? 0 : 250); }); timeGrid.addEventListener("click", event =&gt; { const button = event.target.closest(".time-card"); if (!button) return; state.time = button.dataset.time; setPressed(timeGrid, ".time-card", button); const dish = getRecommendation( state.mood, state.preference, state.time ); if (dish) showRecommendation(dish); }); resetBtn.addEventListener("click", resetFinder); /* ============================================================ MODAL — FOCUS MANAGEMENT + ESC + CLICK OUTSIDE ============================================================ */ function getFocusableInModal() { return $$( 'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])', modal ); } function trapFocus(event) { if (modal.hidden) return; if (event.key === "Escape") { event.preventDefault(); closeModal(); return; } if (event.key !== "Tab") return; const focusable = getFocusableInModal(); if (!focusable.length) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (event.shiftKey &amp;&amp; document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey &amp;&amp; document.activeElement === last) { event.preventDefault(); first.focus(); } } function openModal(dish, returnFocusElement = document.activeElement) { if (!dish) return; state.lastFocusedElement = returnFocusElement || null; modalBody.innerHTML = ` &lt;div class="modal-dish-emoji" aria-hidden="true"&gt;${dish.emoji}&lt;/div&gt; &lt;h3 id="modal-title"&gt;${dish.name}&lt;/h3&gt; &lt;p class="modal-description" id="modal-description"&gt;${dish.description}&lt;/p&gt; &lt;div class="dish-detail"&gt; &lt;strong&gt;⏱ وقت التحضير:&lt;/strong&gt; ${dish.time} دقيقة &lt;/div&gt; &lt;div class="dish-detail"&gt; &lt;strong&gt;🏷️ التصنيفات:&lt;/strong&gt; ${dish.tags.join(" · ")} &lt;/div&gt; &lt;div class="dish-detail"&gt; &lt;strong&gt;🥄 المكونات:&lt;/strong&gt; &lt;ul&gt; ${dish.ingredients.map(item =&gt; `&lt;li&gt;${item}&lt;/li&gt;`).join("")} &lt;/ul&gt; &lt;/div&gt; &lt;div class="dish-detail"&gt; &lt;strong&gt;❤️ مستوى الراحة:&lt;/strong&gt; ${"⭐".repeat(dish.comfortLevel)}${"☆".repeat(5 - dish.comfortLevel)} &lt;/div&gt; `; modal.hidden = false; document.body.classList.add("modal-open"); window.setTimeout(() =&gt; { modalClose.focus(); }, prefersReducedMotion ? 0 : 50); } function closeModal() { if (modal.hidden) return; modal.hidden = true; document.body.classList.remove("modal-open"); const returnTarget = state.lastFocusedElement; state.lastFocusedElement = null; if (returnTarget &amp;&amp; typeof returnTarget.focus === "function") { window.setTimeout(() =&gt; returnTarget.focus({ preventScroll: true }), 0); } } modal.addEventListener("keydown", trapFocus); modalClose.addEventListener("click", closeModal); modalOverlay.addEventListener("click", closeModal); exploreBtn.addEventListener("click", () =&gt; { if (state.currentDish) { openModal(state.currentDish, exploreBtn); } }); /* ============================================================ MENU RENDERING ============================================================ */ function filterData(filter) { if (filter === "all") return dishes; if (filter === "quick") return dishes.filter(dish =&gt; dish.time &lt;= 30); return dishes.filter(dish =&gt; dish.category === filter); } function renderDishes(filter = "all") { state.activeFilter = filter; const filtered = filterData(filter); if (!filtered.length) { dishGrid.innerHTML = ` &lt;p style="grid-column:1/-1;text-align:center;color:var(--brown-light);"&gt; لا توجد أطباق في هذه الفئة. &lt;/p&gt; `; return; } dishGrid.innerHTML = filtered.map(dish =&gt; ` &lt;article class="dish-card" role="listitem"&gt; &lt;span class="dish-emoji" aria-hidden="true"&gt;${dish.emoji}&lt;/span&gt; &lt;div class="dish-name"&gt;${dish.name}&lt;/div&gt; &lt;div class="dish-category"&gt;${categoryNames[dish.category]}&lt;/div&gt; &lt;div class="dish-tags"&gt; ${dish.tags.map(tag =&gt; `&lt;span class="dish-tag"&gt;${tag}&lt;/span&gt;`).join("")} &lt;/div&gt; &lt;button class="btn btn-secondary dish-explore" type="button" data-dish-id="${dish.id}" aria-label="استكشف تفاصيل ${dish.name}"&gt; استكشف الطبق &lt;/button&gt; &lt;/article&gt; `).join(""); $$("button.dish-explore", dishGrid).forEach(button =&gt; { button.addEventListener("click", event =&gt; { const dish = dishes.find(item =&gt; item.id === event.currentTarget.dataset.dishId); if (dish) openModal(dish, event.currentTarget); }); }); } $$(".filter-btn").forEach(button =&gt; { button.addEventListener("click", () =&gt; { $$(".filter-btn").forEach(item =&gt; { item.classList.remove("active"); item.setAttribute("aria-pressed", "false"); }); button.classList.add("active"); button.setAttribute("aria-pressed", "true"); renderDishes(button.dataset.filter); }); }); /* ============================================================ NAVIGATION ============================================================ */ function setNavOpen(open) { navMenu.classList.toggle("open", open); navToggle.setAttribute("aria-expanded", String(open)); } navToggle.addEventListener("click", () =&gt; { setNavOpen(!navMenu.classList.contains("open")); }); $$("a", navMenu).forEach(link =&gt; { link.addEventListener("click", () =&gt; { setNavOpen(false); $$("a", navMenu).forEach(item =&gt; item.removeAttribute("aria-current")); if (link.getAttribute("href") !== "#main") { link.setAttribute("aria-current", "page"); } }); }); document.addEventListener("click", event =&gt; { if ( navMenu.classList.contains("open") &amp;&amp; !navMenu.contains(event.target) &amp;&amp; !navToggle.contains(event.target) ) { setNavOpen(false); } }); window.addEventListener("resize", () =&gt; { if (window.innerWidth &gt; 768) setNavOpen(false); }); /* ============================================================ INIT ============================================================ */ renderDishes("all"); console.log("🍲 Comfort Table — V1.4 Final"); console.log("👨‍💻 Designed &amp; Developed by Jamil Al-Hattami"); console.log("🏢 Jamil Al-Hattami Co. for Digital Marketing &amp; E-Commerce"); console.log("📦 DEV Frontend Challenge — Comfort Food Edition"); console.log("🇾🇪 Yemeni &amp; Arabic comfort food"); console.log("⚙️ Weighted Scoring: Mood 40% + Preference 30% + Time 20% + Comfort 10%"); console.log("🔍 Quick filter: time ≤ 30 minutes"); </code></pre></div> <p>

Top comments (0)