Files
3dealer/CLAUDE.md

216 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Front-end Guidelines
This is a Laravel 12 e-commerce project using Lunar PHP (headless). These guidelines apply to all front-end work.
---
## Project Language Settings
<!-- CHANGE THESE PER PROJECT -->
- **Greek register:** `singular` — use the informal second person (εσύ/σου/σε). e.g. "Η κριτική σου", "Το όνομά σου", "Το email σου"
<!-- singular = informal (εσύ) | plural = formal (εσείς) -->
All UI text written for this project must follow the register above.
---
## Localization
Uses classic Laravel localization (https://laravel.com/docs/13.x/localization) — no third-party i18n routing package.
**Locales:** Greek (`el`) is primary/default, English (`en`) is secondary. Set in `config('app.available_locales')` (`config/app.php`).
**URL structure:** every route is prefixed with `{locale}`, including the default — `3dealer.gr/el/...` and `3dealer.gr/en/...`. Bare root (`/`) 301-redirects to `/el` (see `routes/web.php`). This avoids the ambiguity of an unprefixed default locale (see the reasoning in project memory / past conversation — prefixing every locale keeps hreflang symmetrical and scales cleanly to a third language later).
**URL slugs are the same across both locales, permanently** — e.g. `/el/products/{x}` and `/en/products/{x}`, not `/el/proionta/{x}` vs `/en/products/{x}`. This is a deliberate, standing decision for this project, not a placeholder to revisit later. Only the visible content is translated, not the slug. Do not introduce per-locale translated slugs (e.g. a `lang/{locale}/routes.php` segment map) unless the user explicitly asks for that to change.
**How it's wired:**
- All routes live inside `Route::prefix('{locale}')->where('locale', ...)->middleware('setlocale')` in `routes/web.php`.
- `App\Http\Middleware\SetLocale` validates the `{locale}` segment (404s if not in `available_locales`), calls `App::setLocale()`, and shares `$currentLocale`, `$altLocale`, and `$altLocaleUrl` (the same page in the other locale) to every view — this is what powers both the header's language switcher and the `<head>` hreflang tags in `resources/views/layouts/app.blade.php`. Don't recompute this logic elsewhere; read those shared variables instead.
- When adding a new route, put it inside that `{locale}` group and give it a route `name()` — the alt-locale URL generation in the middleware depends on the current route being named (falls back to the locale root if unnamed).
- When linking to a page in Blade, prefer `route('name', [...])` (locale is a normal route param, defaults to `app()->getLocale()` implicitly via the shared route group) over hand-built path strings, except for pages that don't have a controller/route yet (e.g. `/products`, `/contact`, `/cart` placeholders in the header currently use `url('/'.app()->getLocale().'/products')` — replace with `route()` once those pages exist).
- **Every controller action for a route inside the `{locale}` group must declare `$locale` as its first parameter, even if unused** — e.g. `show(string $locale, Product $product)`. Laravel's `ControllerDispatcher` ultimately calls the controller with `...array_values($parameters)`, i.e. **positionally**. If a route-bound model parameter (like `$product`) isn't preceded by a matching `$locale` parameter in the method signature, the resolved values shift out of position and the wrong value (the locale string) gets passed where the model was expected — a `TypeError` that's easy to misread as a binding failure. Closures with zero declared parameters are unaffected (PHP just ignores the extra positional arg), so this only bites real controller methods.
**Where translatable strings go:**
- Most UI copy is editable by the client/marketing team via **Stoic** (the headless CMS) — don't hardcode it here.
- Small strings that don't need client editing (nav labels, aria-labels, tab labels, pluralized counts) go in Laravel's own lang files at **`lang/{locale}/*.php`** (project root, per Laravel 12+ convention — not `resources/lang`). Current file: `lang/el/general.php` / `lang/en/general.php`. Use `__('general.key')` or `trans_choice('general.key', $count, [...])` for pluralized strings.
---
## Tech Stack
- **Laravel 12** + **Lunar PHP 1.3** (headless e-commerce)
- **Blade** for templating (`@extends`, `@section`, `@yield`, components)
- **Tailwind CSS v4** via `@tailwindcss/vite` — zero config, `@import 'tailwindcss'` only
- **Stimulus JS** for interactivity — controllers registered in `resources/js/stimulus/index.js`
- **Popover API** (native browser) for overlays — no JS overlay libraries
- SQLite database, `npm run dev` for local asset compilation
---
## Key File Locations
| What | Where |
|---|---|
| CSS entry point | `resources/css/app.css` |
| Font definitions | `resources/css/fonts.css` |
| JS entry point | `resources/js/app.js` |
| Stimulus controllers | `resources/js/stimulus/` |
| JS utilities | `resources/js/utils/` |
| Layout | `resources/views/layouts/app.blade.php` |
| UI components (atomic) | `resources/views/components/ui/` |
| Section components | `resources/views/components/` |
| Page views | `resources/views/{page}/` |
| Public assets | `public/images/` |
---
## Design Reference
The Bluebeard template (`resources/css/bluebeard.css`) is used as a **reference only** for colors, spacing, typography, and effects. It is never imported or used directly. Everything is translated to Tailwind utilities or custom CSS where unavoidable.
The boboko project at `/Users/farenoubi/Projects/boboko-test` can be referenced for CSS architecture patterns.
---
## Tailwind vs. Custom CSS
**Prefer Tailwind utilities by default.** Write custom CSS only when something genuinely cannot be expressed as a utility:
- Pseudo-elements (`::before`, `::after`) with dynamic transforms or transitions
- Complex descendant/sibling selectors (e.g. `.group:hover .nav-dropdown`)
- Keyframe animations
- The underline-slide animation on nav links (background-size trick)
**Never** write inline CSS (`style="..."`). **Never** write custom CSS for something Tailwind already covers.
When a component requires a CSS class (e.g. as a hook for a pseudo-element), still put all properties that can be Tailwind utilities as classes on the element in the component file. The CSS rule should contain only what genuinely cannot be a utility — pseudo-elements, complex selectors, keyframes. Do not default to putting all styles in the CSS class just because the class exists.
When custom CSS is needed, add it to `resources/css/app.css` inside `@layer components`. Keep the rule minimal — only what can't be a utility.
---
## Design Tokens
Defined in `@theme {}` in `app.css`:
- `--font-sans`: Manrope (body text)
- `--font-display`: Manrope (headings, nav, buttons) — kept separate from `--font-sans` in case they diverge later
Reuse existing colors (`neutral-200`, `black`), font sizes, spacing, and border styles as components are built. Do not invent new values — check what's already in use first.
---
## Component Structure
### `resources/views/components/ui/` — atomic, reusable UI elements
Single-purpose, stateless or minimally stateful elements used across many contexts:
- `button.blade.php` ✓
- `input.blade.php`
- `input-group.blade.php`
- `icon.blade.php`
- `image.blade.php`
- `tabs.blade.php` / `tab.blade.php`
- `accordion.blade.php`
- `tooltip.blade.php`
- `badge.blade.php`
- etc.
### `resources/views/components/` — section-level or composite components
Larger reusable blocks made of several elements, often with real content:
- `header.blade.php` ✓
- `footer.blade.php`
- `cta.blade.php`
- `product-card.blade.php`
- `contact-section.blade.php`
- etc.
---
## Interactivity
### Popover API for overlays
For **tooltips, modals, dropdowns, and any overlay**: use the native [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) with CSS — no JS, no libraries.
```html
<button popovertarget="my-popover">Open</button>
<div id="my-popover" popover>...</div>
```
Style open/closed states with `[popover]` and `:popover-open` selectors in CSS.
### Stimulus JS for behavior
Use Stimulus when JS is genuinely needed (form handling, cart updates, dynamic state, etc.).
- Keep controllers lean: manage state and coordinate the DOM, don't build UI inside them
- Use Stimulus values, targets, and outlets — avoid reading from the DOM imperatively
- **Never inject HTML strings or classes from inside a controller.** Instead, drive appearance via data attributes or CSS classes toggled on existing elements, and let CSS handle the visual result
- Never write inline JS (`onclick="..."`, etc.)
### Pure CSS for simple interactions
Hover dropdowns, focus states, active states — handle with CSS (`:hover`, `:focus-within`, `.group:hover`) before reaching for Stimulus.
---
## Accessibility (ARIA)
Always add ARIA attributes. This is not optional:
- Interactive elements: `aria-label`, `aria-expanded`, `aria-controls`, `aria-haspopup` as appropriate
- Images: meaningful `alt` text, or `alt=""` + `aria-hidden="true"` for decorative images
- Icons used as buttons: `aria-label` on the button, `aria-hidden="true"` on the SVG
- Form fields: always associated `<label>`, `aria-describedby` for hints/errors
- Dynamic content: `aria-live` regions where content updates without navigation
- When refactoring or adding interactivity, review ARIA impact — don't break existing roles
---
## Greek Typography
CSS `text-transform: uppercase` leaves Greek tonos accents in place (e.g. Ά instead of Α), which is incorrect. The utility at `resources/js/utils/strip-accents.js` handles this automatically for any element with the `uppercase` Tailwind class. **Always add the `uppercase` class to elements** rather than applying `text-transform: uppercase` in CSS — this keeps the strip-accents utility working without extra configuration.
---
## Performance & Core Web Vitals
Every frontend change should assume it will be measured by PageSpeed/Lighthouse. These rules exist because we hit each of these mistakes in production and had to fix them after the fact — bake them in up front instead.
### CSS bundling
- Before adding a CSS import to a shared/global entry point (e.g. `app.css`), ask whether every page actually needs it. Page- or component-specific styles (date pickers, legal-page typography, admin-only widgets) should not ship on pages that don't use them.
- Scope non-critical CSS to load only where it's used — either a dedicated build entry for that page, or import the `.css` file inside the same JS module that already lazy-loads that component (e.g. a Stimulus/JS controller), so the bundler emits it as a separate chunk fetched only when that component actually mounts.
- Exception: never defer CSS for anything the page shows immediately on load (above-the-fold content, or a component that renders as soon as its JS runs — an FAQ accordion, a cookie-consent banner). Even a well-scoped async CSS chunk arrives a beat after the JS that displays the element, causing a visible flash of unstyled/wrong-state content and a layout shift. Keep that CSS in the blocking bundle on purpose.
- Never import a full icon-font library (Phosphor, Font Awesome, etc.) — it ships every icon regardless of usage. Use inline SVGs for only the icons actually referenced; check the shared icon component for an existing icon before adding a new one.
- Only declare design tokens / CSS custom properties that are actually used somewhere in the codebase (Tailwind v4 already tree-shakes unused `@theme` tokens from output, so this is for keeping the source legible, not for bytes).
### Fonts
- `font-display: swap` means any text using a font weight will visibly reflow once that weight's file finishes downloading, if the fallback font's metrics differ. Preload every font weight used in above-the-fold text, not just the default body-copy weight — bold/black heading weights are usually the most visually prominent element on the page and cause the most visible reflow if not preloaded.
- When introducing a new heavy-weight text utility (e.g. a bold/black heading class) in a hero or above-the-fold component, confirm that weight has a matching `<link rel="preload" as="font">` in the layout `<head>`.
### Images
- Any image that could be the Largest Contentful Paint element (hero/banner images) must have: `fetchpriority="high"`, `loading="eager"`, explicit `width`/`height`, a responsive `srcset`/`sizes`, and a `<link rel="preload" as="image">` in `<head>` with matching `imagesrcset`/`imagesizes` — this lets the browser start the fetch as soon as it parses `<head>`, instead of waiting to discover the `<img>` tag in the body.
- Every other image: `loading="lazy"` with explicit `width`/`height` to reserve its layout space.
### Layout shift (CLS)
- Never animate `top`/`bottom`/`left`/`right`/`width`/`height`/`margin` on any element that appears or moves without direct user interaction (banners, toasts, slide-in modals, accordions). These are layout-affecting properties — the browser recalculates layout on every animation frame, and each frame's position delta counts toward CLS. Animate `transform` (translate/scale) and `opacity` instead — both are compositor-only and fully excluded from CLS scoring, no matter how far or long the animation runs.
- This applies to vendor/third-party CSS too (cookie-consent widgets, modal libraries) — check its default show/hide mechanism before shipping, and override it if it animates a layout property directly.
- Any element with a collapsed/expanded default state (accordions, dropdowns) needs its collapsed-state CSS in the blocking stylesheet, never a lazily-loaded chunk — otherwise it renders in its expanded/default state for a frame before the async CSS applies.
---
## Forms
Never add `novalidate` to forms. Always use the browser's default validation.
---
## Asking Questions
When the approach is unclear — layout structure, whether something should be a component, which Tailwind pattern fits — **ask before building**. A short question is cheaper than a refactor.