Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -23,7 +23,6 @@ DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true"
|
|||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"localhost",
|
"localhost",
|
||||||
"testserver",
|
|
||||||
os.getenv("HOST_FQDN", ""),
|
os.getenv("HOST_FQDN", ""),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -151,13 +150,9 @@ STATIC_URL = 'static/'
|
|||||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
directory
|
BASE_DIR / 'static',
|
||||||
for directory in [
|
BASE_DIR / 'assets',
|
||||||
BASE_DIR / 'static',
|
BASE_DIR / 'node_modules',
|
||||||
BASE_DIR / 'assets',
|
|
||||||
BASE_DIR / 'node_modules',
|
|
||||||
]
|
|
||||||
if directory.exists()
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,34 +1,3 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Category, ContactInquiry, Product
|
# Register your models here.
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Category)
|
|
||||||
class CategoryAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("name", "sort_order")
|
|
||||||
prepopulated_fields = {"slug": ("name",)}
|
|
||||||
search_fields = ("name",)
|
|
||||||
ordering = ("sort_order", "name")
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Product)
|
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("name", "category", "price_from", "is_featured", "is_active")
|
|
||||||
list_filter = ("category", "is_featured", "is_active")
|
|
||||||
list_editable = ("is_featured", "is_active")
|
|
||||||
prepopulated_fields = {"slug": ("name",)}
|
|
||||||
search_fields = ("name", "short_description", "description", "material")
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ContactInquiry)
|
|
||||||
class ContactInquiryAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("name", "subject", "preferred_contact_method", "status", "created_at")
|
|
||||||
list_filter = ("preferred_contact_method", "status", "created_at")
|
|
||||||
list_editable = ("status",)
|
|
||||||
search_fields = ("name", "email", "phone", "subject", "message")
|
|
||||||
autocomplete_fields = ("product",)
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.site_header = "Thelen Atelier Admin"
|
|
||||||
admin.site.site_title = "Thelen Atelier"
|
|
||||||
admin.site.index_title = "Katalog & Anfragen verwalten"
|
|
||||||
|
|||||||
@ -1,22 +1,13 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
def project_context(request):
|
def project_context(request):
|
||||||
"""
|
"""
|
||||||
Adds project-specific environment variables to the template context globally.
|
Adds project-specific environment variables to the template context globally.
|
||||||
"""
|
"""
|
||||||
store_phone_display = os.getenv("STORE_PHONE", "0214 41243")
|
|
||||||
store_phone_link = re.sub(r"[^\d+]", "", store_phone_display)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
||||||
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
||||||
"store_name": os.getenv("STORE_NAME", "Juwelier Thelen"),
|
# Used for cache-busting static assets
|
||||||
"store_phone_display": store_phone_display,
|
|
||||||
"store_phone_link": store_phone_link,
|
|
||||||
"store_email": os.getenv("STORE_EMAIL", "info@juwelierthelen.de"),
|
|
||||||
"store_address": os.getenv("STORE_ADDRESS", "Wiesdorfer Platz 59, 51373 Leverkusen"),
|
|
||||||
"deployment_timestamp": int(time.time()),
|
"deployment_timestamp": int(time.time()),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
|
|
||||||
from .models import ContactInquiry, Product
|
|
||||||
|
|
||||||
|
|
||||||
class ContactInquiryForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = ContactInquiry
|
|
||||||
fields = [
|
|
||||||
"product",
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"phone",
|
|
||||||
"preferred_contact_method",
|
|
||||||
"subject",
|
|
||||||
"message",
|
|
||||||
]
|
|
||||||
widgets = {
|
|
||||||
"message": forms.Textarea(attrs={"rows": 5}),
|
|
||||||
}
|
|
||||||
labels = {
|
|
||||||
"product": "Produkt / Kollektion",
|
|
||||||
"name": "Ihr Name",
|
|
||||||
"email": "E-Mail-Adresse",
|
|
||||||
"phone": "Telefonnummer",
|
|
||||||
"preferred_contact_method": "Bevorzugte Kontaktart",
|
|
||||||
"subject": "Betreff",
|
|
||||||
"message": "Nachricht",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields["product"].queryset = Product.objects.filter(is_active=True).select_related("category")
|
|
||||||
self.fields["product"].required = False
|
|
||||||
self.fields["product"].empty_label = "Allgemeine Beratung"
|
|
||||||
|
|
||||||
placeholders = {
|
|
||||||
"name": "z. B. Anna Becker",
|
|
||||||
"email": "anna@example.com",
|
|
||||||
"phone": "+49 ...",
|
|
||||||
"subject": "z. B. Beratung zu Trauringen",
|
|
||||||
"message": "Worum geht es? Teilen Sie uns kurz Ihre Wünsche mit.",
|
|
||||||
}
|
|
||||||
for name, field in self.fields.items():
|
|
||||||
widget = field.widget
|
|
||||||
css_class = "form-select" if isinstance(widget, forms.Select) else "form-control"
|
|
||||||
existing = widget.attrs.get("class", "")
|
|
||||||
widget.attrs["class"] = f"{existing} {css_class}".strip()
|
|
||||||
if name in placeholders:
|
|
||||||
widget.attrs["placeholder"] = placeholders[name]
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super().clean()
|
|
||||||
preferred_contact_method = cleaned_data.get("preferred_contact_method")
|
|
||||||
phone = (cleaned_data.get("phone") or "").strip()
|
|
||||||
product = cleaned_data.get("product")
|
|
||||||
subject = (cleaned_data.get("subject") or "").strip()
|
|
||||||
|
|
||||||
if preferred_contact_method == ContactInquiry.PreferredContactMethod.PHONE and not phone:
|
|
||||||
self.add_error("phone", "Bitte geben Sie eine Telefonnummer an, wenn Sie einen Rückruf wünschen.")
|
|
||||||
|
|
||||||
if product and not subject:
|
|
||||||
cleaned_data["subject"] = f"Anfrage zu {product.name}"
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2026-04-06 09:11
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Category',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=120, unique=True)),
|
|
||||||
('slug', models.SlugField(unique=True)),
|
|
||||||
('description', models.TextField(blank=True)),
|
|
||||||
('sort_order', models.PositiveIntegerField(default=0)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ['sort_order', 'name'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Product',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=160)),
|
|
||||||
('slug', models.SlugField(unique=True)),
|
|
||||||
('short_description', models.CharField(max_length=220)),
|
|
||||||
('description', models.TextField()),
|
|
||||||
('material', models.CharField(blank=True, max_length=120)),
|
|
||||||
('price_from', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
|
||||||
('collection_note', models.CharField(blank=True, max_length=180)),
|
|
||||||
('is_featured', models.BooleanField(default=False)),
|
|
||||||
('is_active', models.BooleanField(default=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='core.category')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ['name'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ContactInquiry',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=120)),
|
|
||||||
('email', models.EmailField(max_length=254)),
|
|
||||||
('phone', models.CharField(blank=True, max_length=40)),
|
|
||||||
('subject', models.CharField(blank=True, max_length=160)),
|
|
||||||
('message', models.TextField()),
|
|
||||||
('preferred_contact_method', models.CharField(choices=[('phone', 'Telefon'), ('email', 'E-Mail')], default='phone', max_length=10)),
|
|
||||||
('status', models.CharField(choices=[('new', 'Neu'), ('contacted', 'Kontaktiert'), ('closed', 'Abgeschlossen')], default='new', max_length=20)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inquiries', to='core.product')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ['-created_at'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def seed_catalog(apps, schema_editor):
|
|
||||||
Category = apps.get_model("core", "Category")
|
|
||||||
Product = apps.get_model("core", "Product")
|
|
||||||
|
|
||||||
categories = {
|
|
||||||
"trauringe": Category.objects.update_or_create(
|
|
||||||
slug="trauringe",
|
|
||||||
defaults={
|
|
||||||
"name": "Trauringe",
|
|
||||||
"description": "Beratung für Trauringe, Materialien, Oberflächen und Gravuren.",
|
|
||||||
"sort_order": 1,
|
|
||||||
},
|
|
||||||
)[0],
|
|
||||||
"schmuck": Category.objects.update_or_create(
|
|
||||||
slug="schmuck",
|
|
||||||
defaults={
|
|
||||||
"name": "Schmuck",
|
|
||||||
"description": "Ringe, Ohrschmuck und Perlenstücke für besondere Momente.",
|
|
||||||
"sort_order": 2,
|
|
||||||
},
|
|
||||||
)[0],
|
|
||||||
"uhren": Category.objects.update_or_create(
|
|
||||||
slug="uhren",
|
|
||||||
defaults={
|
|
||||||
"name": "Uhren",
|
|
||||||
"description": "Ausgewählte Uhren mit persönlicher Beratung und Service.",
|
|
||||||
"sort_order": 3,
|
|
||||||
},
|
|
||||||
)[0],
|
|
||||||
"service": Category.objects.update_or_create(
|
|
||||||
slug="service",
|
|
||||||
defaults={
|
|
||||||
"name": "Services",
|
|
||||||
"description": "Goldschmiedearbeiten, Reparaturen, Reinigung und Extras.",
|
|
||||||
"sort_order": 4,
|
|
||||||
},
|
|
||||||
)[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
products = [
|
|
||||||
{
|
|
||||||
"slug": "signature-trauringe",
|
|
||||||
"category": categories["trauringe"],
|
|
||||||
"name": "Signature Trauringe",
|
|
||||||
"short_description": "Warme Goldtöne, klare Linien und Platz für Ihre persönliche Gravur.",
|
|
||||||
"description": "Diese Beispielkollektion steht für die Trauring-Beratung von Juwelier Thelen: verschiedene Legierungen, angenehme Profile und eine ruhige, persönliche Auswahl im Geschäft.",
|
|
||||||
"material": "Gelb- oder Roségold",
|
|
||||||
"price_from": "1290.00",
|
|
||||||
"collection_note": "Gravur und Größenanpassung möglich",
|
|
||||||
"is_featured": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "perlen-ohrschmuck-edit",
|
|
||||||
"category": categories["schmuck"],
|
|
||||||
"name": "Perlen & Ohrschmuck Edit",
|
|
||||||
"short_description": "Leichte, elegante Schmuckstücke mit klassischer Note und moderner Wirkung.",
|
|
||||||
"description": "Inspiriert von den Serviceleistungen rund um Perlen und Ohrlochstechen: eine kuratierte Auswahl an Ohrschmuck und Perlendesigns für Alltag und Anlass.",
|
|
||||||
"material": "Perlen, Gold, Silber",
|
|
||||||
"price_from": "149.00",
|
|
||||||
"collection_note": "Ideal für Geschenkberatung",
|
|
||||||
"is_featured": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "atelier-automatik-uhr",
|
|
||||||
"category": categories["uhren"],
|
|
||||||
"name": "Atelier Automatik Uhr",
|
|
||||||
"short_description": "Zeitloses Zifferblatt, ausgewogenes Gehäuse und Beratung zu Pflege & Revision.",
|
|
||||||
"description": "Die neue Katalogstrecke macht Uhrenpräsentation und Service greifbarer: ausgewählte Modelle, klare Preisindikationen und direkter Kontakt für Rückfragen.",
|
|
||||||
"material": "Edelstahl",
|
|
||||||
"price_from": "895.00",
|
|
||||||
"collection_note": "Revision und Armbandservice auf Anfrage",
|
|
||||||
"is_featured": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "goldschmiede-service-paket",
|
|
||||||
"category": categories["service"],
|
|
||||||
"name": "Goldschmiede Servicepaket",
|
|
||||||
"short_description": "Ringweiten, Reinigung, Umarbeitung und Reparatur – digital angefragt, persönlich umgesetzt.",
|
|
||||||
"description": "Auch ohne Checkout bleibt der Ablauf komplett: Leistung entdecken, Kontakt aufnehmen und den Auftrag im Geschäft besprechen. Dieses Beispiel zeigt den Service-Fokus der Website.",
|
|
||||||
"material": "Service & Beratung",
|
|
||||||
"price_from": None,
|
|
||||||
"collection_note": "Preis nach Sichtprüfung",
|
|
||||||
"is_featured": True,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for item in products:
|
|
||||||
Product.objects.update_or_create(
|
|
||||||
slug=item["slug"],
|
|
||||||
defaults=item,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def unseed_catalog(apps, schema_editor):
|
|
||||||
Product = apps.get_model("core", "Product")
|
|
||||||
Category = apps.get_model("core", "Category")
|
|
||||||
|
|
||||||
Product.objects.filter(
|
|
||||||
slug__in=[
|
|
||||||
"signature-trauringe",
|
|
||||||
"perlen-ohrschmuck-edit",
|
|
||||||
"atelier-automatik-uhr",
|
|
||||||
"goldschmiede-service-paket",
|
|
||||||
]
|
|
||||||
).delete()
|
|
||||||
Category.objects.filter(slug__in=["trauringe", "schmuck", "uhren", "service"]).delete()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("core", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(seed_catalog, unseed_catalog),
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,81 +1,3 @@
|
|||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
class Category(models.Model):
|
|
||||||
name = models.CharField(max_length=120, unique=True)
|
|
||||||
slug = models.SlugField(unique=True)
|
|
||||||
description = models.TextField(blank=True)
|
|
||||||
sort_order = models.PositiveIntegerField(default=0)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["sort_order", "name"]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Product(models.Model):
|
|
||||||
category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="products")
|
|
||||||
name = models.CharField(max_length=160)
|
|
||||||
slug = models.SlugField(unique=True)
|
|
||||||
short_description = models.CharField(max_length=220)
|
|
||||||
description = models.TextField()
|
|
||||||
material = models.CharField(max_length=120, blank=True)
|
|
||||||
price_from = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
|
|
||||||
collection_note = models.CharField(max_length=180, blank=True)
|
|
||||||
is_featured = models.BooleanField(default=False)
|
|
||||||
is_active = models.BooleanField(default=True)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["name"]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def price_label(self):
|
|
||||||
if self.price_from is None:
|
|
||||||
return "Preis auf Anfrage"
|
|
||||||
quantized = self.price_from.quantize(Decimal("1.00"))
|
|
||||||
return f"ab {quantized:,.2f} €".replace(",", "X").replace(".", ",").replace("X", ".")
|
|
||||||
|
|
||||||
|
|
||||||
class ContactInquiry(models.Model):
|
|
||||||
class PreferredContactMethod(models.TextChoices):
|
|
||||||
PHONE = "phone", "Telefon"
|
|
||||||
EMAIL = "email", "E-Mail"
|
|
||||||
|
|
||||||
class Status(models.TextChoices):
|
|
||||||
NEW = "new", "Neu"
|
|
||||||
CONTACTED = "contacted", "Kontaktiert"
|
|
||||||
CLOSED = "closed", "Abgeschlossen"
|
|
||||||
|
|
||||||
product = models.ForeignKey(
|
|
||||||
Product,
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name="inquiries",
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
name = models.CharField(max_length=120)
|
|
||||||
email = models.EmailField()
|
|
||||||
phone = models.CharField(max_length=40, blank=True)
|
|
||||||
subject = models.CharField(max_length=160, blank=True)
|
|
||||||
message = models.TextField()
|
|
||||||
preferred_contact_method = models.CharField(
|
|
||||||
max_length=10,
|
|
||||||
choices=PreferredContactMethod.choices,
|
|
||||||
default=PreferredContactMethod.PHONE,
|
|
||||||
)
|
|
||||||
status = models.CharField(max_length=20, choices=Status.choices, default=Status.NEW)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["-created_at"]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.name} – {self.subject or 'Allgemeine Anfrage'}"
|
|
||||||
|
|||||||
@ -1,81 +1,25 @@
|
|||||||
{% load static %}
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
||||||
<title>{{ meta_title|default:store_name }}</title>
|
{% if project_description %}
|
||||||
<meta name="description" content="{{ meta_description|default:project_description|default:'Moderner Schmuck- und Trauringkatalog mit persönlicher Beratung.' }}">
|
<meta name="description" content="{{ project_description }}">
|
||||||
|
<meta property="og:description" content="{{ project_description }}">
|
||||||
|
<meta property="twitter:description" content="{{ project_description }}">
|
||||||
|
{% endif %}
|
||||||
{% if project_image_url %}
|
{% if project_image_url %}
|
||||||
<meta property="og:image" content="{{ project_image_url }}">
|
<meta property="og:image" content="{{ project_image_url }}">
|
||||||
<meta property="twitter:image" content="{{ project_image_url }}">
|
<meta property="twitter:image" content="{{ project_image_url }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
{% load static %}
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="site-chrome">
|
{% block content %}{% endblock %}
|
||||||
<header class="site-header sticky-top">
|
|
||||||
<nav class="navbar navbar-expand-lg">
|
|
||||||
<div class="container py-2">
|
|
||||||
<a class="navbar-brand" href="{% url 'home' %}">
|
|
||||||
<span class="brand-mark">T</span>
|
|
||||||
<span>
|
|
||||||
<strong>{{ store_name }}</strong>
|
|
||||||
<small>Leverkusen</small>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Navigation umschalten">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="mainNav">
|
|
||||||
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'home' %}">Start</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'catalog' %}">Katalog</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'home' %}#services">Services</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{% url 'contact' %}">Kontakt</a></li>
|
|
||||||
<li class="nav-item ms-lg-2"><a class="btn btn-brand btn-sm px-3" href="/admin/">Admin</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="site-footer">
|
|
||||||
<div class="container py-5">
|
|
||||||
<div class="row g-4 align-items-start">
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<p class="eyebrow mb-2">Tradition trifft moderne Navigation</p>
|
|
||||||
<h2 class="footer-title">Persönliche Beratung für Schmuck, Trauringe und Uhren.</h2>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-lg-3">
|
|
||||||
<h3 class="footer-heading">Besuchen</h3>
|
|
||||||
<p>{{ store_address }}</p>
|
|
||||||
<a href="tel:{{ store_phone_link }}">{{ store_phone_display }}</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 col-lg-2">
|
|
||||||
<h3 class="footer-heading">Kontakt</h3>
|
|
||||||
<p><a href="mailto:{{ store_email }}">{{ store_email }}</a></p>
|
|
||||||
<p><a href="{% url 'contact' %}">Anfrage senden</a></p>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-2">
|
|
||||||
<h3 class="footer-heading">Verwaltung</h3>
|
|
||||||
<p><a href="/admin/">Zum Admin</a></p>
|
|
||||||
<p><a href="{% url 'catalog' %}">Katalog</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js" integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q" crossorigin="anonymous"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="page-hero section-shell page-hero-compact">
|
|
||||||
<div class="container">
|
|
||||||
<span class="eyebrow">Katalog</span>
|
|
||||||
<h1 class="page-title">Entdecken Sie Schmuck, Uhren und Trauringe.</h1>
|
|
||||||
<p class="page-copy">Filterbar, fokussiert und bereit für die direkte Kontaktaufnahme – ohne komplizierten Checkout.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-shell pt-0">
|
|
||||||
<div class="container">
|
|
||||||
<div class="glass-panel filter-panel mb-4">
|
|
||||||
<form method="get" class="row g-3 align-items-end">
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<label class="form-label" for="q">Suche</label>
|
|
||||||
<input class="form-control" id="q" type="search" name="q" value="{{ query }}" placeholder="z. B. Gravur, Perlen, Automatikuhr">
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<label class="form-label" for="category">Kategorie</label>
|
|
||||||
<select class="form-select" id="category" name="category">
|
|
||||||
<option value="">Alle Kategorien</option>
|
|
||||||
{% for category in categories %}
|
|
||||||
<option value="{{ category.slug }}" {% if selected_category == category.slug %}selected{% endif %}>{{ category.name }} ({{ category.product_total }})</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-3 d-flex gap-3">
|
|
||||||
<button class="btn btn-brand flex-grow-1" type="submit">Filtern</button>
|
|
||||||
<a class="btn btn-ghost flex-grow-1" href="{% url 'catalog' %}">Zurücksetzen</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-4">
|
|
||||||
{% for product in products %}
|
|
||||||
<div class="col-md-6 col-xl-4">
|
|
||||||
<article class="product-card glass-panel h-100">
|
|
||||||
<div class="product-visual">
|
|
||||||
<span>{{ product.category.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-body">
|
|
||||||
<p class="product-category">{{ product.category.name }}</p>
|
|
||||||
<h2>{{ product.name }}</h2>
|
|
||||||
<p>{{ product.short_description }}</p>
|
|
||||||
<div class="product-meta">
|
|
||||||
<strong class="product-price">{{ product.price_label }}</strong>
|
|
||||||
{% if product.material %}<span>{{ product.material }}</span>{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-3 mt-4">
|
|
||||||
<a class="btn btn-brand" href="{% url 'product_detail' product.slug %}">Details</a>
|
|
||||||
<a class="btn btn-ghost" href="{% url 'contact' %}?product={{ product.slug }}">Anfragen</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
{% empty %}
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="empty-state glass-panel">
|
|
||||||
<h2>Keine Treffer gefunden</h2>
|
|
||||||
<p>Passen Sie die Filter an oder nehmen Sie direkt Kontakt auf, damit wir Ihnen passende Stücke empfehlen können.</p>
|
|
||||||
<div class="d-flex flex-wrap justify-content-center gap-3">
|
|
||||||
<a class="btn btn-brand" href="{% url 'catalog' %}">Alle Produkte ansehen</a>
|
|
||||||
<a class="btn btn-ghost" href="{% url 'contact' %}">Beratung anfragen</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="page-hero section-shell page-hero-compact">
|
|
||||||
<div class="container">
|
|
||||||
<span class="eyebrow">Kontakt</span>
|
|
||||||
<h1 class="page-title">Schnell anfragen, persönlich beraten lassen.</h1>
|
|
||||||
<p class="page-copy">Die Anfrage wird gespeichert, im Admin sichtbar gemacht und auf Wunsch zusätzlich per E-Mail versendet.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-shell pt-0">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<aside class="glass-panel contact-info h-100">
|
|
||||||
<h2>Direkter Kontakt</h2>
|
|
||||||
<div class="contact-method">
|
|
||||||
<span>Telefon</span>
|
|
||||||
<a href="tel:{{ store_phone_link }}">{{ store_phone_display }}</a>
|
|
||||||
</div>
|
|
||||||
<div class="contact-method">
|
|
||||||
<span>E-Mail</span>
|
|
||||||
<a href="mailto:{{ store_email }}">{{ store_email }}</a>
|
|
||||||
</div>
|
|
||||||
<div class="contact-method">
|
|
||||||
<span>Adresse</span>
|
|
||||||
<p>{{ store_address }}</p>
|
|
||||||
</div>
|
|
||||||
{% if selected_product %}
|
|
||||||
<div class="selected-product-note">
|
|
||||||
<p class="eyebrow mb-2">Vorgewählt</p>
|
|
||||||
<strong>{{ selected_product.name }}</strong>
|
|
||||||
<p class="mb-0">Ihre Anfrage startet bereits mit dem passenden Produktbezug.</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="glass-panel form-panel">
|
|
||||||
<div class="section-heading mb-4">
|
|
||||||
<span class="eyebrow">Anfrageformular</span>
|
|
||||||
<h2>Per E-Mail anfragen</h2>
|
|
||||||
</div>
|
|
||||||
<form method="post" novalidate>
|
|
||||||
{% csrf_token %}
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger">{{ form.non_field_errors }}</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="row g-3">
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="{% if field.name == 'message' %}col-12{% else %}col-md-6{% endif %}">
|
|
||||||
<label class="form-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
|
|
||||||
{{ field }}
|
|
||||||
{% if field.help_text %}<div class="form-text">{{ field.help_text }}</div>{% endif %}
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<div class="field-error">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-column flex-md-row gap-3 align-items-md-center mt-4">
|
|
||||||
<button class="btn btn-brand btn-lg" type="submit">Anfrage absenden</button>
|
|
||||||
<p class="form-note mb-0">Wir melden uns zeitnah telefonisch oder per E-Mail zurück.</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="section-shell success-shell">
|
|
||||||
<div class="container">
|
|
||||||
<div class="glass-panel success-card mx-auto">
|
|
||||||
<span class="eyebrow">Vielen Dank</span>
|
|
||||||
<h1 class="page-title">Ihre Anfrage ist eingegangen.</h1>
|
|
||||||
<p class="page-copy">Das Team von {{ store_name }} meldet sich so schnell wie möglich bei Ihnen.</p>
|
|
||||||
{% if inquiry %}
|
|
||||||
<div class="success-summary text-start">
|
|
||||||
<div><strong>Kontakt:</strong> {{ inquiry.name }}</div>
|
|
||||||
<div><strong>Rückmeldung via:</strong> {{ inquiry.get_preferred_contact_method_display }}</div>
|
|
||||||
<div><strong>Produkt:</strong> {% if inquiry.product %}{{ inquiry.product.name }}{% else %}Allgemeine Beratung{% endif %}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="d-flex flex-wrap justify-content-center gap-3 mt-4">
|
|
||||||
<a class="btn btn-brand btn-lg" href="{% url 'catalog' %}">Zurück zum Katalog</a>
|
|
||||||
<a class="btn btn-ghost btn-lg" href="tel:{{ store_phone_link }}">Direkt anrufen</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,171 +1,145 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
|
||||||
|
{% block title %}{{ project_name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-color-start: #6a11cb;
|
||||||
|
--bg-color-end: #2575fc;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||||
|
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
|
||||||
|
animation: bg-pan 20s linear infinite;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bg-pan {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--card-bg-color);
|
||||||
|
border: 1px solid var(--card-border-color);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 1.2rem;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.25);
|
||||||
|
border-top-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtime code {
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
padding: 0.15rem 0.45rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="hero-section section-shell">
|
<main>
|
||||||
<div class="container position-relative">
|
<div class="card">
|
||||||
<div class="row align-items-center gy-5">
|
<h1>Analyzing your requirements and generating your app…</h1>
|
||||||
<div class="col-lg-7">
|
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||||
<span class="eyebrow">Seit über 80 Jahren Vertrauen, Qualität und persönliche Beratung</span>
|
<span class="sr-only">Loading…</span>
|
||||||
<h1 class="hero-title">Ein moderner Schmuckkatalog für Trauringe, Uhren und Goldschmiedeservice.</h1>
|
|
||||||
<p class="hero-copy">Entdecken Sie ausgewählte Kollektionen, lernen Sie unsere Services kennen und wechseln Sie mit einem Klick vom Stöbern zur persönlichen Beratung – telefonisch oder per E-Mail.</p>
|
|
||||||
<form class="hero-search glass-panel" method="get" action="{% url 'catalog' %}">
|
|
||||||
<label class="form-label" for="hero-search">Wonach suchen Sie?</label>
|
|
||||||
<div class="d-flex flex-column flex-md-row gap-3">
|
|
||||||
<input class="form-control form-control-lg" id="hero-search" type="search" name="q" placeholder="z. B. Trauringe, Perlen, Automatikuhr">
|
|
||||||
<button class="btn btn-brand btn-lg" type="submit">Katalog durchsuchen</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="hero-actions d-flex flex-wrap gap-3">
|
|
||||||
<a class="btn btn-brand btn-lg" href="{% url 'catalog' %}">Kollektion ansehen</a>
|
|
||||||
<a class="btn btn-ghost btn-lg" href="tel:{{ store_phone_link }}">Jetzt anrufen</a>
|
|
||||||
<a class="btn btn-ghost btn-lg" href="{% url 'contact' %}">E-Mail senden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<div class="hero-showcase glass-panel">
|
|
||||||
<div class="hero-orb hero-orb-lg"></div>
|
|
||||||
<div class="hero-orb hero-orb-sm"></div>
|
|
||||||
<p class="eyebrow mb-3">Store Snapshot</p>
|
|
||||||
<div class="stat-grid">
|
|
||||||
<article class="stat-card">
|
|
||||||
<strong>80+</strong>
|
|
||||||
<span>Jahre Erfahrung</span>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<strong>4</strong>
|
|
||||||
<span>klare Katalogbereiche</span>
|
|
||||||
</article>
|
|
||||||
<article class="stat-card">
|
|
||||||
<strong>1</strong>
|
|
||||||
<span>zentrale Anfrage pro Produkt</span>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<div class="showcase-note">
|
|
||||||
<p class="mb-1"><strong>Wiesdorfer Platz 59</strong></p>
|
|
||||||
<p class="mb-0">51373 Leverkusen · Beratung per Telefon und E-Mail</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
|
||||||
|
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
||||||
|
<p class="runtime">
|
||||||
|
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
||||||
|
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</main>
|
||||||
|
<footer>
|
||||||
<section class="section-shell pt-0">
|
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
||||||
<div class="container">
|
</footer>
|
||||||
<div class="row g-4">
|
{% endblock %}
|
||||||
{% for category in categories %}
|
|
||||||
<div class="col-md-6 col-xl-3">
|
|
||||||
<a class="category-card glass-panel h-100" href="{% url 'catalog' %}?category={{ category.slug }}">
|
|
||||||
<span class="category-chip">{{ category.product_total }} Einträge</span>
|
|
||||||
<h2>{{ category.name }}</h2>
|
|
||||||
<p>{{ category.description }}</p>
|
|
||||||
<span class="text-link">Kategorie öffnen</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-shell" id="services">
|
|
||||||
<div class="container">
|
|
||||||
<div class="section-heading text-center mx-auto">
|
|
||||||
<span class="eyebrow">Leistungen</span>
|
|
||||||
<h2>Die wichtigsten Inhalte der bisherigen Website – jetzt klarer, schneller, moderner.</h2>
|
|
||||||
<p>Die neue Startseite bündelt Trauringe, Schmuck, Uhren und Serviceleistungen in einem ruhigen, leicht navigierbaren Ablauf.</p>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
{% for item in service_highlights %}
|
|
||||||
<div class="col-md-6 col-xl-3">
|
|
||||||
<article class="service-card glass-panel h-100">
|
|
||||||
<div class="service-icon">{{ forloop.counter|stringformat:"02d" }}</div>
|
|
||||||
<h3>{{ item.title }}</h3>
|
|
||||||
<p>{{ item.text }}</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-shell pt-0">
|
|
||||||
<div class="container">
|
|
||||||
<div class="section-heading d-flex flex-column flex-lg-row align-items-lg-end justify-content-between gap-3">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow">Auswahl</span>
|
|
||||||
<h2>Featured Katalog</h2>
|
|
||||||
</div>
|
|
||||||
<a class="btn btn-ghost" href="{% url 'catalog' %}">Alle Produkte ansehen</a>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
{% for product in featured_products %}
|
|
||||||
<div class="col-md-6 col-xl-3">
|
|
||||||
<article class="product-card glass-panel h-100">
|
|
||||||
<div class="product-visual">
|
|
||||||
<span>{{ product.category.name }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-body">
|
|
||||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
|
|
||||||
<div>
|
|
||||||
<p class="product-category">{{ product.category.name }}</p>
|
|
||||||
<h3>{{ product.name }}</h3>
|
|
||||||
</div>
|
|
||||||
<strong class="product-price">{{ product.price_label }}</strong>
|
|
||||||
</div>
|
|
||||||
<p>{{ product.short_description }}</p>
|
|
||||||
<a class="text-link mt-auto" href="{% url 'product_detail' product.slug %}">Details & Anfrage</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
{% empty %}
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="empty-state glass-panel">
|
|
||||||
<h3>Der Katalog ist bereit.</h3>
|
|
||||||
<p>Pflegen Sie jetzt Produkte im Admin ein, damit die Startseite automatisch die ersten Highlights zeigt.</p>
|
|
||||||
<a class="btn btn-brand" href="/admin/">Produkte im Admin anlegen</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-shell">
|
|
||||||
<div class="container">
|
|
||||||
<div class="process-shell glass-panel">
|
|
||||||
<div class="section-heading mb-4">
|
|
||||||
<span class="eyebrow">Ablauf</span>
|
|
||||||
<h2>Vom Produkt zur persönlichen Beratung in drei einfachen Schritten.</h2>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
{% for step in process_steps %}
|
|
||||||
<div class="col-md-4">
|
|
||||||
<article class="process-card">
|
|
||||||
<span class="process-step">{{ step.step }}</span>
|
|
||||||
<h3>{{ step.title }}</h3>
|
|
||||||
<p>{{ step.text }}</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-shell pt-0">
|
|
||||||
<div class="container">
|
|
||||||
<div class="contact-banner glass-panel">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow">Kontakt</span>
|
|
||||||
<h2>Sie möchten lieber direkt sprechen?</h2>
|
|
||||||
<p>Rufen Sie an oder senden Sie eine E-Mail – alle Produktseiten führen direkt in die Kontaktstrecke.</p>
|
|
||||||
</div>
|
|
||||||
<div class="contact-banner-actions d-flex flex-wrap gap-3">
|
|
||||||
<a class="btn btn-brand btn-lg" href="tel:{{ store_phone_link }}">{{ store_phone_display }}</a>
|
|
||||||
<a class="btn btn-ghost btn-lg" href="mailto:{{ store_email }}">{{ store_email }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="page-hero section-shell page-hero-compact">
|
|
||||||
<div class="container">
|
|
||||||
<a class="back-link" href="{% url 'catalog' %}">← Zurück zum Katalog</a>
|
|
||||||
<div class="row g-4 align-items-center mt-2">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<span class="eyebrow">{{ product.category.name }}</span>
|
|
||||||
<h1 class="page-title">{{ product.name }}</h1>
|
|
||||||
<p class="page-copy">{{ product.short_description }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<div class="glass-panel detail-card">
|
|
||||||
<p class="detail-price">{{ product.price_label }}</p>
|
|
||||||
{% if product.material %}<p class="detail-label">Material · {{ product.material }}</p>{% endif %}
|
|
||||||
{% if product.collection_note %}<p class="detail-label">{{ product.collection_note }}</p>{% endif %}
|
|
||||||
<div class="d-grid gap-3 mt-4">
|
|
||||||
<a class="btn btn-brand btn-lg" href="{% url 'contact' %}?product={{ product.slug }}">Per E-Mail anfragen</a>
|
|
||||||
<a class="btn btn-ghost btn-lg" href="tel:{{ store_phone_link }}">{{ store_phone_display }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-shell pt-0">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<article class="glass-panel detail-copy h-100">
|
|
||||||
<h2>Über dieses Stück</h2>
|
|
||||||
<p>{{ product.description }}</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<aside class="glass-panel detail-side h-100">
|
|
||||||
<h2>Warum diese neue Produktseite besser funktioniert</h2>
|
|
||||||
<ul class="detail-list">
|
|
||||||
<li>Klare Preis- und Materialhinweise.</li>
|
|
||||||
<li>Sofort sichtbare Kontaktoptionen per Telefon und E-Mail.</li>
|
|
||||||
<li>Direkte Verbindung in den Admin-gestützten Anfrage-Workflow.</li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{% if related_products %}
|
|
||||||
<section class="section-shell pt-0">
|
|
||||||
<div class="container">
|
|
||||||
<div class="section-heading">
|
|
||||||
<span class="eyebrow">Passend dazu</span>
|
|
||||||
<h2>Weitere Stücke aus {{ product.category.name }}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
{% for item in related_products %}
|
|
||||||
<div class="col-md-6 col-xl-4">
|
|
||||||
<article class="product-card glass-panel h-100">
|
|
||||||
<div class="product-visual"><span>{{ item.category.name }}</span></div>
|
|
||||||
<div class="product-body">
|
|
||||||
<p class="product-category">{{ item.category.name }}</p>
|
|
||||||
<h3>{{ item.name }}</h3>
|
|
||||||
<p>{{ item.short_description }}</p>
|
|
||||||
<a class="text-link mt-auto" href="{% url 'product_detail' item.slug %}">Details ansehen</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,47 +1,3 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from .models import Category, ContactInquiry, Product
|
# Create your tests here.
|
||||||
|
|
||||||
|
|
||||||
class CatalogFlowTests(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.category = Category.objects.create(
|
|
||||||
name="Trauringe",
|
|
||||||
slug="trauringe",
|
|
||||||
description="Individuelle Ringe und Beratung.",
|
|
||||||
sort_order=1,
|
|
||||||
)
|
|
||||||
self.product = Product.objects.create(
|
|
||||||
category=self.category,
|
|
||||||
name="Signature Trauring",
|
|
||||||
slug="signature-trauring",
|
|
||||||
short_description="Ein klassischer Ring mit warmem Goldton.",
|
|
||||||
description="Ausgewählte Trauringe mit Gravuroption und persönlicher Beratung.",
|
|
||||||
material="Gold",
|
|
||||||
price_from="1290.00",
|
|
||||||
is_featured=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_homepage_uses_catalog_content(self):
|
|
||||||
response = self.client.get(reverse("home"))
|
|
||||||
self.assertContains(response, self.product.name)
|
|
||||||
self.assertContains(response, self.category.name)
|
|
||||||
|
|
||||||
def test_contact_form_creates_inquiry(self):
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("contact"),
|
|
||||||
{
|
|
||||||
"product": self.product.pk,
|
|
||||||
"name": "Anna Becker",
|
|
||||||
"email": "anna@example.com",
|
|
||||||
"phone": "+49 123",
|
|
||||||
"preferred_contact_method": "phone",
|
|
||||||
"subject": "Beratung",
|
|
||||||
"message": "Ich möchte einen Termin für Trauringe.",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertRedirects(response, reverse("contact_success"))
|
|
||||||
self.assertEqual(ContactInquiry.objects.count(), 1)
|
|
||||||
inquiry = ContactInquiry.objects.first()
|
|
||||||
self.assertEqual(inquiry.product, self.product)
|
|
||||||
|
|||||||
@ -1,11 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import catalog, contact, contact_success, home, product_detail
|
from .views import home
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", home, name="home"),
|
path("", home, name="home"),
|
||||||
path("katalog/", catalog, name="catalog"),
|
|
||||||
path("katalog/<slug:slug>/", product_detail, name="product_detail"),
|
|
||||||
path("kontakt/", contact, name="contact"),
|
|
||||||
path("kontakt/danke/", contact_success, name="contact_success"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
195
core/views.py
195
core/views.py
@ -1,186 +1,25 @@
|
|||||||
from django.conf import settings
|
import os
|
||||||
from django.core.mail import send_mail
|
import platform
|
||||||
from django.db.models import Count, Q
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
|
||||||
|
|
||||||
from .forms import ContactInquiryForm
|
from django import get_version as django_version
|
||||||
from .models import Category, ContactInquiry, Product
|
from django.shortcuts import render
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
SERVICE_HIGHLIGHTS = [
|
|
||||||
{
|
|
||||||
"title": "Trauringe & Beratung",
|
|
||||||
"text": "Individuelle Beratung für Trauringe, Materialien und Gravuren – persönlich im Geschäft oder per Anfrage.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Goldschmiedeservice",
|
|
||||||
"text": "Ringweitenänderung, Reinigung, Umarbeitung und Reparaturen mit viel Erfahrung und Fingerspitzengefühl.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Uhren & Revision",
|
|
||||||
"text": "Ausgewählte Uhren und fachkundige Unterstützung bei Pflege, Wartung und Reparatur wertvoller Zeitmesser.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Perlen, Ohrschmuck & Extras",
|
|
||||||
"text": "Perlkettenservice, Ohrlochstechen mit STUDEX-System sowie Geschenkgutscheine für besondere Anlässe.",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
PROCESS_STEPS = [
|
|
||||||
{
|
|
||||||
"step": "01",
|
|
||||||
"title": "Kollektion entdecken",
|
|
||||||
"text": "Filtern Sie die Kollektion nach Kategorie und lassen Sie sich inspirieren.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": "02",
|
|
||||||
"title": "Per Telefon oder E-Mail anfragen",
|
|
||||||
"text": "Zu jedem Produkt gelangen Sie in wenigen Klicks zur passenden Kontaktmöglichkeit.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": "03",
|
|
||||||
"title": "Persönliche Beratung im Store",
|
|
||||||
"text": "Im Geschäft in Leverkusen klärt das Team Details, Anpassungen und Verfügbarkeit.",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _catalog_categories():
|
|
||||||
return Category.objects.annotate(
|
|
||||||
product_total=Count("products", filter=Q(products__is_active=True))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _send_inquiry_notification(inquiry):
|
|
||||||
if not settings.CONTACT_EMAIL_TO:
|
|
||||||
return
|
|
||||||
|
|
||||||
subject = inquiry.subject or "Neue Anfrage über die Website"
|
|
||||||
lines = [
|
|
||||||
f"Name: {inquiry.name}",
|
|
||||||
f"E-Mail: {inquiry.email}",
|
|
||||||
f"Telefon: {inquiry.phone or '-'}",
|
|
||||||
f"Bevorzugte Kontaktart: {inquiry.get_preferred_contact_method_display()}",
|
|
||||||
f"Produkt: {inquiry.product.name if inquiry.product else 'Allgemeine Beratung'}",
|
|
||||||
"",
|
|
||||||
inquiry.message,
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
send_mail(
|
|
||||||
subject=f"Website-Anfrage: {subject}",
|
|
||||||
message="\n".join(lines),
|
|
||||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
||||||
recipient_list=settings.CONTACT_EMAIL_TO,
|
|
||||||
fail_silently=True,
|
|
||||||
reply_to=[inquiry.email],
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
featured_products = Product.objects.filter(is_active=True, is_featured=True).select_related("category")[:4]
|
"""Render the landing screen with loader and environment details."""
|
||||||
categories = _catalog_categories().filter(product_total__gt=0)
|
host_name = request.get_host().lower()
|
||||||
|
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"meta_title": "Juwelier Thelen | Moderner Schmuck- & Trauringkatalog",
|
"project_name": "New Style",
|
||||||
"meta_description": "Moderner Katalog für Trauringe, Schmuck, Uhren und Goldschmiedeservice – mit schneller Kontaktaufnahme per Telefon oder E-Mail.",
|
"agent_brand": agent_brand,
|
||||||
"featured_products": featured_products,
|
"django_version": django_version(),
|
||||||
"categories": categories,
|
"python_version": platform.python_version(),
|
||||||
"service_highlights": SERVICE_HIGHLIGHTS,
|
"current_time": now,
|
||||||
"process_steps": PROCESS_STEPS,
|
"host_name": host_name,
|
||||||
|
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
||||||
|
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
||||||
}
|
}
|
||||||
return render(request, "core/index.html", context)
|
return render(request, "core/index.html", context)
|
||||||
|
|
||||||
|
|
||||||
def catalog(request):
|
|
||||||
selected_category = request.GET.get("category", "").strip()
|
|
||||||
query = request.GET.get("q", "").strip()
|
|
||||||
|
|
||||||
products = Product.objects.filter(is_active=True).select_related("category")
|
|
||||||
if selected_category:
|
|
||||||
products = products.filter(category__slug=selected_category)
|
|
||||||
if query:
|
|
||||||
products = products.filter(
|
|
||||||
Q(name__icontains=query)
|
|
||||||
| Q(short_description__icontains=query)
|
|
||||||
| Q(description__icontains=query)
|
|
||||||
| Q(material__icontains=query)
|
|
||||||
| Q(category__name__icontains=query)
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"meta_title": "Katalog | Juwelier Thelen",
|
|
||||||
"meta_description": "Entdecken Sie Trauringe, Schmuck und Uhren – klar gefiltert und bereit für eine persönliche Beratung.",
|
|
||||||
"products": products,
|
|
||||||
"categories": _catalog_categories(),
|
|
||||||
"selected_category": selected_category,
|
|
||||||
"query": query,
|
|
||||||
}
|
|
||||||
return render(request, "core/catalog.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
def product_detail(request, slug):
|
|
||||||
product = get_object_or_404(
|
|
||||||
Product.objects.filter(is_active=True).select_related("category"),
|
|
||||||
slug=slug,
|
|
||||||
)
|
|
||||||
related_products = (
|
|
||||||
Product.objects.filter(is_active=True, category=product.category)
|
|
||||||
.exclude(pk=product.pk)
|
|
||||||
.select_related("category")[:3]
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"meta_title": f"{product.name} | Juwelier Thelen",
|
|
||||||
"meta_description": product.short_description,
|
|
||||||
"product": product,
|
|
||||||
"related_products": related_products,
|
|
||||||
}
|
|
||||||
return render(request, "core/product_detail.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
def contact(request):
|
|
||||||
selected_product = None
|
|
||||||
product_slug = request.GET.get("product", "").strip()
|
|
||||||
|
|
||||||
if product_slug:
|
|
||||||
selected_product = Product.objects.filter(is_active=True, slug=product_slug).first()
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
form = ContactInquiryForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
inquiry = form.save()
|
|
||||||
_send_inquiry_notification(inquiry)
|
|
||||||
request.session["contact_inquiry_id"] = inquiry.pk
|
|
||||||
return redirect("contact_success")
|
|
||||||
else:
|
|
||||||
initial = {}
|
|
||||||
if selected_product:
|
|
||||||
initial["product"] = selected_product
|
|
||||||
initial["subject"] = f"Anfrage zu {selected_product.name}"
|
|
||||||
form = ContactInquiryForm(initial=initial)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"meta_title": "Kontakt | Juwelier Thelen",
|
|
||||||
"meta_description": "Fragen zu Trauringen, Schmuck, Uhren oder Serviceleistungen? Kontaktieren Sie Juwelier Thelen per Telefon oder E-Mail.",
|
|
||||||
"form": form,
|
|
||||||
"selected_product": selected_product,
|
|
||||||
}
|
|
||||||
return render(request, "core/contact.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
def contact_success(request):
|
|
||||||
inquiry_id = request.session.get("contact_inquiry_id")
|
|
||||||
inquiry = None
|
|
||||||
if inquiry_id:
|
|
||||||
inquiry = ContactInquiry.objects.filter(pk=inquiry_id).select_related("product").first()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"meta_title": "Anfrage erhalten | Juwelier Thelen",
|
|
||||||
"meta_description": "Vielen Dank für Ihre Anfrage. Das Team meldet sich zeitnah telefonisch oder per E-Mail zurück.",
|
|
||||||
"inquiry": inquiry,
|
|
||||||
}
|
|
||||||
return render(request, "core/contact_success.html", context)
|
|
||||||
|
|||||||
@ -1,632 +1,4 @@
|
|||||||
/* Brand system */
|
/* Custom styles for the application */
|
||||||
:root {
|
|
||||||
--color-bg: #f5efe7;
|
|
||||||
--color-bg-soft: #ece4d7;
|
|
||||||
--color-surface: rgba(255, 250, 244, 0.82);
|
|
||||||
--color-surface-strong: #fffaf4;
|
|
||||||
--color-ink: #171616;
|
|
||||||
--color-muted: #5f605f;
|
|
||||||
--color-brand: #b08a52;
|
|
||||||
--color-brand-dark: #8d6a37;
|
|
||||||
--color-accent: #1d4339;
|
|
||||||
--color-accent-soft: #dce9e3;
|
|
||||||
--color-line: rgba(23, 22, 22, 0.08);
|
|
||||||
--shadow-soft: 0 24px 70px rgba(28, 24, 19, 0.12);
|
|
||||||
--shadow-card: 0 18px 40px rgba(43, 31, 18, 0.1);
|
|
||||||
--radius-xl: 32px;
|
|
||||||
--radius-lg: 24px;
|
|
||||||
--radius-md: 18px;
|
|
||||||
--radius-sm: 14px;
|
|
||||||
--space-section: clamp(4.5rem, 7vw, 7rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
||||||
color: var(--color-ink);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgba(176, 138, 82, 0.18), transparent 30%),
|
|
||||||
radial-gradient(circle at 85% 15%, rgba(29, 67, 57, 0.14), transparent 26%),
|
|
||||||
linear-gradient(180deg, #fbf7f1 0%, var(--color-bg) 46%, #efe8dc 100%);
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
content: "";
|
|
||||||
position: fixed;
|
|
||||||
inset: auto;
|
|
||||||
border-radius: 50%;
|
|
||||||
filter: blur(10px);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
width: 18rem;
|
|
||||||
height: 18rem;
|
|
||||||
top: 6rem;
|
|
||||||
right: 4vw;
|
|
||||||
background: rgba(176, 138, 82, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
width: 12rem;
|
|
||||||
height: 12rem;
|
|
||||||
bottom: 10rem;
|
|
||||||
left: 3vw;
|
|
||||||
background: rgba(29, 67, 57, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
.navbar-brand strong,
|
|
||||||
.footer-title {
|
|
||||||
font-family: 'Cormorant Garamond', Georgia, serif;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
line-height: 1.05;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header {
|
|
||||||
background: rgba(251, 247, 241, 0.78);
|
|
||||||
backdrop-filter: blur(24px);
|
|
||||||
border-bottom: 1px solid rgba(23, 22, 22, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.85rem;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand small {
|
|
||||||
display: block;
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 0.78rem;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-mark {
|
|
||||||
width: 2.6rem;
|
|
||||||
height: 2.6rem;
|
|
||||||
display: inline-grid;
|
|
||||||
place-items: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(135deg, var(--color-brand), #ddc08b);
|
|
||||||
color: white;
|
|
||||||
font-family: 'Cormorant Garamond', Georgia, serif;
|
|
||||||
font-size: 1.45rem;
|
|
||||||
box-shadow: 0 12px 30px rgba(176, 138, 82, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
color: var(--color-ink);
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 0.75rem 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover,
|
|
||||||
.nav-link:focus {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-shell {
|
|
||||||
padding: var(--space-section) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-hero-compact {
|
|
||||||
padding-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title,
|
|
||||||
.page-title {
|
|
||||||
font-size: clamp(3rem, 5vw, 5.4rem);
|
|
||||||
margin-bottom: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-copy,
|
|
||||||
.page-copy,
|
|
||||||
.section-heading p,
|
|
||||||
.product-body p,
|
|
||||||
.service-card p,
|
|
||||||
.process-card p,
|
|
||||||
.contact-info p,
|
|
||||||
.detail-copy p,
|
|
||||||
.detail-side p,
|
|
||||||
.form-note,
|
|
||||||
.footer-title + p {
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 1.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-copy,
|
|
||||||
.page-copy {
|
|
||||||
max-width: 44rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: var(--color-accent);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.18em;
|
|
||||||
font-size: 0.77rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-panel,
|
|
||||||
.product-card,
|
|
||||||
.service-card,
|
|
||||||
.category-card,
|
|
||||||
.empty-state,
|
|
||||||
.process-shell,
|
|
||||||
.success-card {
|
|
||||||
background: linear-gradient(180deg, rgba(255, 250, 244, 0.9), rgba(255, 255, 255, 0.72));
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-search,
|
|
||||||
.filter-panel,
|
|
||||||
.form-panel {
|
|
||||||
padding: 1.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-actions {
|
|
||||||
margin-top: 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-showcase {
|
|
||||||
padding: 2rem;
|
|
||||||
min-height: 27rem;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-orb {
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(176, 138, 82, 0.12));
|
|
||||||
border: 1px solid rgba(176, 138, 82, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-orb-lg {
|
|
||||||
width: 13rem;
|
|
||||||
height: 13rem;
|
|
||||||
top: 2rem;
|
|
||||||
right: -1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-orb-sm {
|
|
||||||
width: 5rem;
|
|
||||||
height: 5rem;
|
|
||||||
left: 1rem;
|
|
||||||
bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-grid {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
padding: 1.4rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(255, 255, 255, 0.72);
|
|
||||||
border: 1px solid rgba(23, 22, 22, 0.05);
|
|
||||||
box-shadow: var(--shadow-card);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card strong {
|
|
||||||
display: block;
|
|
||||||
font-family: 'Cormorant Garamond', Georgia, serif;
|
|
||||||
font-size: 2.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card span,
|
|
||||||
.showcase-note,
|
|
||||||
.product-category,
|
|
||||||
.detail-label {
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.showcase-note {
|
|
||||||
position: absolute;
|
|
||||||
left: 2rem;
|
|
||||||
right: 2rem;
|
|
||||||
bottom: 2rem;
|
|
||||||
padding: 1.1rem 1.2rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(29, 67, 57, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-card,
|
|
||||||
.service-card,
|
|
||||||
.product-card {
|
|
||||||
padding: 1.6rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-card:hover,
|
|
||||||
.service-card:hover,
|
|
||||||
.product-card:hover,
|
|
||||||
.contact-banner:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 28px 70px rgba(28, 24, 19, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-chip,
|
|
||||||
.product-visual span,
|
|
||||||
.process-step {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 0.35rem 0.75rem;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-chip,
|
|
||||||
.process-step {
|
|
||||||
background: rgba(29, 67, 57, 0.09);
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual {
|
|
||||||
min-height: 12rem;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 28% 28%, rgba(255, 255, 255, 0.95), transparent 36%),
|
|
||||||
linear-gradient(140deg, rgba(176, 138, 82, 0.22), rgba(29, 67, 57, 0.18));
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual::before,
|
|
||||||
.product-visual::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual::before {
|
|
||||||
width: 7rem;
|
|
||||||
height: 7rem;
|
|
||||||
right: 1.25rem;
|
|
||||||
top: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual::after {
|
|
||||||
width: 3.6rem;
|
|
||||||
height: 3.6rem;
|
|
||||||
left: 1.4rem;
|
|
||||||
bottom: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual span {
|
|
||||||
position: absolute;
|
|
||||||
left: 1rem;
|
|
||||||
top: 1rem;
|
|
||||||
background: rgba(255, 250, 244, 0.9);
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-body h2,
|
|
||||||
.product-body h3,
|
|
||||||
.service-card h3,
|
|
||||||
.category-card h2,
|
|
||||||
.process-card h3,
|
|
||||||
.contact-banner h2,
|
|
||||||
.contact-info h2,
|
|
||||||
.detail-copy h2,
|
|
||||||
.detail-side h2,
|
|
||||||
.form-panel h2 {
|
|
||||||
font-size: clamp(1.8rem, 2vw, 2.4rem);
|
|
||||||
margin-bottom: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-category {
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price,
|
|
||||||
.detail-price {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: auto;
|
|
||||||
padding-top: 1rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link,
|
|
||||||
.back-link {
|
|
||||||
color: var(--color-accent);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link:hover,
|
|
||||||
.back-link:hover {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-heading {
|
|
||||||
max-width: 42rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-heading h2,
|
|
||||||
.footer-title {
|
|
||||||
font-size: clamp(2.4rem, 3vw, 3.6rem);
|
|
||||||
margin-bottom: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-icon {
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
display: inline-grid;
|
|
||||||
place-items: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background: rgba(176, 138, 82, 0.12);
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.process-shell,
|
|
||||||
.contact-banner,
|
|
||||||
.success-card,
|
|
||||||
.detail-card,
|
|
||||||
.detail-copy,
|
|
||||||
.detail-side,
|
|
||||||
.contact-info {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.process-card {
|
|
||||||
padding: 1.2rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(255, 255, 255, 0.55);
|
|
||||||
border: 1px solid rgba(23, 22, 22, 0.05);
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-banner {
|
|
||||||
padding: 2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-method {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-method span,
|
|
||||||
.footer-heading {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.35rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 0.84rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-method a,
|
|
||||||
.site-footer a {
|
|
||||||
font-size: 1.05rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-product-note {
|
|
||||||
margin-top: 2rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(29, 67, 57, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-footer {
|
|
||||||
border-top: 1px solid rgba(23, 22, 22, 0.06);
|
|
||||||
background: rgba(255, 250, 244, 0.65);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 0.9rem 1.45rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
border-width: 1px;
|
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover,
|
|
||||||
.btn:focus {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-brand {
|
|
||||||
color: #fff;
|
|
||||||
background: linear-gradient(135deg, var(--color-brand-dark), var(--color-brand));
|
|
||||||
border-color: transparent;
|
|
||||||
box-shadow: 0 16px 32px rgba(176, 138, 82, 0.28);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-brand:hover,
|
|
||||||
.btn-brand:focus {
|
|
||||||
color: #fff;
|
|
||||||
background: linear-gradient(135deg, #7a5b2f, var(--color-brand-dark));
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost {
|
|
||||||
color: var(--color-ink);
|
|
||||||
background: rgba(255, 255, 255, 0.58);
|
|
||||||
border-color: rgba(23, 22, 22, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost:hover,
|
|
||||||
.btn-ghost:focus {
|
|
||||||
background: rgba(255, 255, 255, 0.88);
|
|
||||||
border-color: rgba(23, 22, 22, 0.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control,
|
|
||||||
.form-select {
|
|
||||||
min-height: 3.4rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
border: 1px solid rgba(23, 22, 22, 0.1);
|
|
||||||
background: rgba(255, 255, 255, 0.92);
|
|
||||||
color: var(--color-ink);
|
|
||||||
padding: 0.85rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.form-control {
|
|
||||||
min-height: 9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus,
|
|
||||||
.form-select:focus,
|
|
||||||
.btn:focus,
|
|
||||||
.nav-link:focus,
|
|
||||||
a:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: rgba(176, 138, 82, 0.5);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(176, 138, 82, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-error {
|
|
||||||
color: #9b2d2d;
|
|
||||||
font-size: 0.88rem;
|
|
||||||
margin-top: 0.45rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state,
|
|
||||||
.success-card {
|
|
||||||
text-align: center;
|
|
||||||
padding: 3rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-card {
|
|
||||||
max-width: 48rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-summary {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
padding: 1.2rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(29, 67, 57, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-panel {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-list {
|
|
||||||
padding-left: 1.1rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-list li {
|
|
||||||
margin-bottom: 0.8rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-link {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 991.98px) {
|
|
||||||
.hero-showcase {
|
|
||||||
min-height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-banner {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.section-shell {
|
|
||||||
padding: 4rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title,
|
|
||||||
.page-title {
|
|
||||||
font-size: 2.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-panel,
|
|
||||||
.product-card,
|
|
||||||
.service-card,
|
|
||||||
.category-card,
|
|
||||||
.contact-banner,
|
|
||||||
.success-card,
|
|
||||||
.process-shell,
|
|
||||||
.detail-card,
|
|
||||||
.detail-copy,
|
|
||||||
.detail-side,
|
|
||||||
.contact-info {
|
|
||||||
border-radius: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,632 +1,21 @@
|
|||||||
/* Brand system */
|
|
||||||
:root {
|
:root {
|
||||||
--color-bg: #f5efe7;
|
--bg-color-start: #6a11cb;
|
||||||
--color-bg-soft: #ece4d7;
|
--bg-color-end: #2575fc;
|
||||||
--color-surface: rgba(255, 250, 244, 0.82);
|
--text-color: #ffffff;
|
||||||
--color-surface-strong: #fffaf4;
|
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||||
--color-ink: #171616;
|
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||||
--color-muted: #5f605f;
|
|
||||||
--color-brand: #b08a52;
|
|
||||||
--color-brand-dark: #8d6a37;
|
|
||||||
--color-accent: #1d4339;
|
|
||||||
--color-accent-soft: #dce9e3;
|
|
||||||
--color-line: rgba(23, 22, 22, 0.08);
|
|
||||||
--shadow-soft: 0 24px 70px rgba(28, 24, 19, 0.12);
|
|
||||||
--shadow-card: 0 18px 40px rgba(43, 31, 18, 0.1);
|
|
||||||
--radius-xl: 32px;
|
|
||||||
--radius-lg: 24px;
|
|
||||||
--radius-md: 18px;
|
|
||||||
--radius-sm: 14px;
|
|
||||||
--space-section: clamp(4.5rem, 7vw, 7rem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
color: var(--color-ink);
|
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||||
background:
|
color: var(--text-color);
|
||||||
radial-gradient(circle at top left, rgba(176, 138, 82, 0.18), transparent 30%),
|
|
||||||
radial-gradient(circle at 85% 15%, rgba(29, 67, 57, 0.14), transparent 26%),
|
|
||||||
linear-gradient(180deg, #fbf7f1 0%, var(--color-bg) 46%, #efe8dc 100%);
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
content: "";
|
|
||||||
position: fixed;
|
|
||||||
inset: auto;
|
|
||||||
border-radius: 50%;
|
|
||||||
filter: blur(10px);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
width: 18rem;
|
|
||||||
height: 18rem;
|
|
||||||
top: 6rem;
|
|
||||||
right: 4vw;
|
|
||||||
background: rgba(176, 138, 82, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
body::after {
|
|
||||||
width: 12rem;
|
|
||||||
height: 12rem;
|
|
||||||
bottom: 10rem;
|
|
||||||
left: 3vw;
|
|
||||||
background: rgba(29, 67, 57, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
.navbar-brand strong,
|
|
||||||
.footer-title {
|
|
||||||
font-family: 'Cormorant Garamond', Georgia, serif;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
line-height: 1.05;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header {
|
|
||||||
background: rgba(251, 247, 241, 0.78);
|
|
||||||
backdrop-filter: blur(24px);
|
|
||||||
border-bottom: 1px solid rgba(23, 22, 22, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.85rem;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand small {
|
|
||||||
display: block;
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 0.78rem;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-mark {
|
|
||||||
width: 2.6rem;
|
|
||||||
height: 2.6rem;
|
|
||||||
display: inline-grid;
|
|
||||||
place-items: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(135deg, var(--color-brand), #ddc08b);
|
|
||||||
color: white;
|
|
||||||
font-family: 'Cormorant Garamond', Georgia, serif;
|
|
||||||
font-size: 1.45rem;
|
|
||||||
box-shadow: 0 12px 30px rgba(176, 138, 82, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
color: var(--color-ink);
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 0.75rem 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover,
|
|
||||||
.nav-link:focus {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-shell {
|
|
||||||
padding: var(--space-section) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-hero-compact {
|
|
||||||
padding-bottom: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title,
|
|
||||||
.page-title {
|
|
||||||
font-size: clamp(3rem, 5vw, 5.4rem);
|
|
||||||
margin-bottom: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-copy,
|
|
||||||
.page-copy,
|
|
||||||
.section-heading p,
|
|
||||||
.product-body p,
|
|
||||||
.service-card p,
|
|
||||||
.process-card p,
|
|
||||||
.contact-info p,
|
|
||||||
.detail-copy p,
|
|
||||||
.detail-side p,
|
|
||||||
.form-note,
|
|
||||||
.footer-title + p {
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 1.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-copy,
|
|
||||||
.page-copy {
|
|
||||||
max-width: 44rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: var(--color-accent);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.18em;
|
|
||||||
font-size: 0.77rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-panel,
|
|
||||||
.product-card,
|
|
||||||
.service-card,
|
|
||||||
.category-card,
|
|
||||||
.empty-state,
|
|
||||||
.process-shell,
|
|
||||||
.success-card {
|
|
||||||
background: linear-gradient(180deg, rgba(255, 250, 244, 0.9), rgba(255, 255, 255, 0.72));
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
|
||||||
box-shadow: var(--shadow-soft);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-search,
|
|
||||||
.filter-panel,
|
|
||||||
.form-panel {
|
|
||||||
padding: 1.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-actions {
|
|
||||||
margin-top: 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-showcase {
|
|
||||||
padding: 2rem;
|
|
||||||
min-height: 27rem;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-orb {
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(176, 138, 82, 0.12));
|
|
||||||
border: 1px solid rgba(176, 138, 82, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-orb-lg {
|
|
||||||
width: 13rem;
|
|
||||||
height: 13rem;
|
|
||||||
top: 2rem;
|
|
||||||
right: -1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-orb-sm {
|
|
||||||
width: 5rem;
|
|
||||||
height: 5rem;
|
|
||||||
left: 1rem;
|
|
||||||
bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-grid {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
padding: 1.4rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(255, 255, 255, 0.72);
|
|
||||||
border: 1px solid rgba(23, 22, 22, 0.05);
|
|
||||||
box-shadow: var(--shadow-card);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card strong {
|
|
||||||
display: block;
|
|
||||||
font-family: 'Cormorant Garamond', Georgia, serif;
|
|
||||||
font-size: 2.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card span,
|
|
||||||
.showcase-note,
|
|
||||||
.product-category,
|
|
||||||
.detail-label {
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.showcase-note {
|
|
||||||
position: absolute;
|
|
||||||
left: 2rem;
|
|
||||||
right: 2rem;
|
|
||||||
bottom: 2rem;
|
|
||||||
padding: 1.1rem 1.2rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(29, 67, 57, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-card,
|
|
||||||
.service-card,
|
|
||||||
.product-card {
|
|
||||||
padding: 1.6rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-card:hover,
|
|
||||||
.service-card:hover,
|
|
||||||
.product-card:hover,
|
|
||||||
.contact-banner:hover {
|
|
||||||
transform: translateY(-4px);
|
|
||||||
box-shadow: 0 28px 70px rgba(28, 24, 19, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-chip,
|
|
||||||
.product-visual span,
|
|
||||||
.process-step {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 999px;
|
|
||||||
padding: 0.35rem 0.75rem;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-chip,
|
|
||||||
.process-step {
|
|
||||||
background: rgba(29, 67, 57, 0.09);
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual {
|
|
||||||
min-height: 12rem;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 28% 28%, rgba(255, 255, 255, 0.95), transparent 36%),
|
|
||||||
linear-gradient(140deg, rgba(176, 138, 82, 0.22), rgba(29, 67, 57, 0.18));
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual::before,
|
|
||||||
.product-visual::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual::before {
|
|
||||||
width: 7rem;
|
|
||||||
height: 7rem;
|
|
||||||
right: 1.25rem;
|
|
||||||
top: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual::after {
|
|
||||||
width: 3.6rem;
|
|
||||||
height: 3.6rem;
|
|
||||||
left: 1.4rem;
|
|
||||||
bottom: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-visual span {
|
|
||||||
position: absolute;
|
|
||||||
left: 1rem;
|
|
||||||
top: 1rem;
|
|
||||||
background: rgba(255, 250, 244, 0.9);
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-body h2,
|
|
||||||
.product-body h3,
|
|
||||||
.service-card h3,
|
|
||||||
.category-card h2,
|
|
||||||
.process-card h3,
|
|
||||||
.contact-banner h2,
|
|
||||||
.contact-info h2,
|
|
||||||
.detail-copy h2,
|
|
||||||
.detail-side h2,
|
|
||||||
.form-panel h2 {
|
|
||||||
font-size: clamp(1.8rem, 2vw, 2.4rem);
|
|
||||||
margin-bottom: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-category {
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price,
|
|
||||||
.detail-price {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
min-height: 100vh;
|
||||||
gap: 1rem;
|
|
||||||
margin-top: auto;
|
|
||||||
padding-top: 1rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link,
|
|
||||||
.back-link {
|
|
||||||
color: var(--color-accent);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link:hover,
|
|
||||||
.back-link:hover {
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-heading {
|
|
||||||
max-width: 42rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-heading h2,
|
|
||||||
.footer-title {
|
|
||||||
font-size: clamp(2.4rem, 3vw, 3.6rem);
|
|
||||||
margin-bottom: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-icon {
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
display: inline-grid;
|
|
||||||
place-items: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background: rgba(176, 138, 82, 0.12);
|
|
||||||
color: var(--color-brand-dark);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.process-shell,
|
|
||||||
.contact-banner,
|
|
||||||
.success-card,
|
|
||||||
.detail-card,
|
|
||||||
.detail-copy,
|
|
||||||
.detail-side,
|
|
||||||
.contact-info {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.process-card {
|
|
||||||
padding: 1.2rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(255, 255, 255, 0.55);
|
|
||||||
border: 1px solid rgba(23, 22, 22, 0.05);
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-banner {
|
|
||||||
padding: 2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-method {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-method span,
|
|
||||||
.footer-heading {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.35rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
font-size: 0.84rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-method a,
|
|
||||||
.site-footer a {
|
|
||||||
font-size: 1.05rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-product-note {
|
|
||||||
margin-top: 2rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(29, 67, 57, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-footer {
|
|
||||||
border-top: 1px solid rgba(23, 22, 22, 0.06);
|
|
||||||
background: rgba(255, 250, 244, 0.65);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 0.9rem 1.45rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
border-width: 1px;
|
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover,
|
|
||||||
.btn:focus {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-brand {
|
|
||||||
color: #fff;
|
|
||||||
background: linear-gradient(135deg, var(--color-brand-dark), var(--color-brand));
|
|
||||||
border-color: transparent;
|
|
||||||
box-shadow: 0 16px 32px rgba(176, 138, 82, 0.28);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-brand:hover,
|
|
||||||
.btn-brand:focus {
|
|
||||||
color: #fff;
|
|
||||||
background: linear-gradient(135deg, #7a5b2f, var(--color-brand-dark));
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost {
|
|
||||||
color: var(--color-ink);
|
|
||||||
background: rgba(255, 255, 255, 0.58);
|
|
||||||
border-color: rgba(23, 22, 22, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost:hover,
|
|
||||||
.btn-ghost:focus {
|
|
||||||
background: rgba(255, 255, 255, 0.88);
|
|
||||||
border-color: rgba(23, 22, 22, 0.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control,
|
|
||||||
.form-select {
|
|
||||||
min-height: 3.4rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
border: 1px solid rgba(23, 22, 22, 0.1);
|
|
||||||
background: rgba(255, 255, 255, 0.92);
|
|
||||||
color: var(--color-ink);
|
|
||||||
padding: 0.85rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.form-control {
|
|
||||||
min-height: 9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus,
|
|
||||||
.form-select:focus,
|
|
||||||
.btn:focus,
|
|
||||||
.nav-link:focus,
|
|
||||||
a:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: rgba(176, 138, 82, 0.5);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(176, 138, 82, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-error {
|
|
||||||
color: #9b2d2d;
|
|
||||||
font-size: 0.88rem;
|
|
||||||
margin-top: 0.45rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state,
|
|
||||||
.success-card {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3rem 2rem;
|
overflow: hidden;
|
||||||
}
|
|
||||||
|
|
||||||
.success-card {
|
|
||||||
max-width: 48rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-summary {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
padding: 1.2rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: rgba(29, 67, 57, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-panel {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-list {
|
|
||||||
padding-left: 1.1rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-list li {
|
|
||||||
margin-bottom: 0.8rem;
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-link {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 991.98px) {
|
|
||||||
.hero-showcase {
|
|
||||||
min-height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-banner {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.section-shell {
|
|
||||||
padding: 4rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title,
|
|
||||||
.page-title {
|
|
||||||
font-size: 2.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-panel,
|
|
||||||
.product-card,
|
|
||||||
.service-card,
|
|
||||||
.category-card,
|
|
||||||
.contact-banner,
|
|
||||||
.success-card,
|
|
||||||
.process-shell,
|
|
||||||
.detail-card,
|
|
||||||
.detail-copy,
|
|
||||||
.detail-side,
|
|
||||||
.contact-info {
|
|
||||||
border-radius: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user