Restructure the repo into a pnpm + Turborepo monorepo: - apps/web: Nuxt 4 site, now server-rendered and reading all content from the API; pages cached with SWR and purged on content changes - apps/api: NestJS 12 + Prisma 7 + PostgreSQL. Auth with short-lived access tokens and rotating httpOnly refresh cookies, roles (ADMIN, EDITOR), catalog, editable site sections, media upload to S3-compatible storage (converted to WebP), contact messages, audit log - apps/admin: Vue 3 admin panel (Ant Design Vue, Pinia, Tailwind) for products, categories, site sections, messages, media and users - packages/shared: Zod schemas and types shared by API and admin Also: - seed script that loads the original 8 categories / 89 products and section content - Docker images for api (with a one-off migrate service), web, admin, and an nginx gateway for both domains - dev and production compose files, daily database backup service - API e2e tests Removes the committed static out/ build and the old root Dockerfile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
3.4 KiB
CSS
119 lines
3.4 KiB
CSS
/*
|
|
* Ant Design Vue ↔ project look.
|
|
* antd's CSS-in-JS is unlayered while Tailwind v4 lives in cascade layers, so Tailwind classes
|
|
* can't restyle antd elements. These unlayered rules pin antd form controls to the exact
|
|
* pre-migration styling (same values as the old Tailwind `inputCls` / submit button).
|
|
*/
|
|
|
|
/* ---- form shell ---- */
|
|
.contact-form.ant-form {
|
|
font-family: inherit;
|
|
font-size: inherit;
|
|
line-height: inherit;
|
|
}
|
|
/* antd resets items and their grid wrappers to 14px/1.5714; inherit like the original markup did
|
|
(the control line box decides the gap under the inline-block textarea) */
|
|
.contact-form .ant-form-item,
|
|
.contact-form .ant-form-item .ant-row,
|
|
.contact-form .ant-form-item .ant-col {
|
|
margin-bottom: 0;
|
|
font-size: inherit;
|
|
line-height: inherit;
|
|
}
|
|
.contact-form .ant-form-item .ant-form-item-label {
|
|
padding: 0 0 8px; /* mb-2 */
|
|
line-height: 16px;
|
|
}
|
|
.contact-form .ant-form-item-label > label {
|
|
width: 100%; /* original labels were full-width blocks */
|
|
height: auto;
|
|
font-size: 12px; /* text-xs */
|
|
line-height: 16px;
|
|
color: var(--color-muted);
|
|
}
|
|
.contact-form .ant-form-item-explain {
|
|
font-size: 12px;
|
|
line-height: 20px;
|
|
padding-top: 4px;
|
|
}
|
|
|
|
/* ---- inputs (= w-full rounded-lg border border-line bg-bg px-4 py-3.5 text-sm text-ink) ---- */
|
|
.contact-form .ant-input {
|
|
width: 100%;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--color-line);
|
|
background-color: var(--color-bg);
|
|
padding: 14px 16px;
|
|
font-family: inherit;
|
|
font-size: 14px;
|
|
line-height: 20px;
|
|
color: var(--color-ink);
|
|
box-shadow: none;
|
|
transition-property: color, background-color, border-color;
|
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
transition-duration: 150ms;
|
|
}
|
|
.contact-form .ant-input::placeholder {
|
|
color: color-mix(in oklab, var(--color-muted) 50%, transparent);
|
|
}
|
|
.contact-form .ant-input:hover {
|
|
border-color: var(--color-line);
|
|
}
|
|
.contact-form .ant-input:focus,
|
|
.contact-form .ant-input-focused {
|
|
border-color: var(--color-accent);
|
|
outline: none;
|
|
box-shadow: none;
|
|
}
|
|
/* antd's .ant-input-rtl forces direction:rtl; honour an explicit dir="ltr" (phone field) */
|
|
.contact-form .ant-input[dir="ltr"] {
|
|
direction: ltr;
|
|
text-align: left;
|
|
}
|
|
.contact-form textarea.ant-input {
|
|
resize: none;
|
|
min-height: 0;
|
|
vertical-align: baseline; /* native inline-block textarea alignment (antd sets bottom) */
|
|
}
|
|
|
|
/* ---- submit (= rounded-full bg-accent py-4 font-bold text-bg, hover:bg-accent-soft + glow) ---- */
|
|
.contact-form .ant-btn.contact-submit {
|
|
display: flex;
|
|
width: 100%;
|
|
height: auto;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
border: 0;
|
|
border-radius: calc(infinity * 1px); /* = tailwind rounded-full */
|
|
background-color: var(--color-accent);
|
|
padding: 16px 0;
|
|
font-family: inherit;
|
|
font-size: 16px;
|
|
line-height: 24px;
|
|
font-weight: 700;
|
|
color: var(--color-bg);
|
|
box-shadow: none;
|
|
cursor: default;
|
|
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
.contact-form .ant-btn.contact-submit > span {
|
|
display: inline;
|
|
}
|
|
@media (hover: hover) {
|
|
.contact-form .ant-btn.contact-submit:hover {
|
|
background-color: var(--color-accent-soft);
|
|
color: var(--color-bg);
|
|
box-shadow: 0 8px 45px rgba(255, 90, 0, 0.45);
|
|
}
|
|
}
|
|
.contact-form .ant-btn.contact-submit:disabled {
|
|
opacity: 0.6;
|
|
border: 0;
|
|
}
|
|
/* no click-wave ring (wasn't part of the original button) */
|
|
.contact-form .ant-btn.contact-submit::after,
|
|
.contact-form .ant-wave {
|
|
display: none;
|
|
}
|