1
This commit is contained in:
parent
0fbe1d175a
commit
062a594521
563
assets/css/custom.css
Normal file
563
assets/css/custom.css
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
/* --- Base & Variables --- */
|
||||||
|
:root {
|
||||||
|
--color-primary: #6C63FF;
|
||||||
|
--color-secondary: #FF6584;
|
||||||
|
--font-family-headings: 'Poppins', sans-serif;
|
||||||
|
--font-family-body: 'Roboto', sans-serif;
|
||||||
|
--base-spacing: 1rem;
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
|
||||||
|
/* Light Mode */
|
||||||
|
--bg-color: #F8F9FA;
|
||||||
|
--surface-color: #FFFFFF;
|
||||||
|
--text-color: #212529;
|
||||||
|
--subtle-text-color: #6c757d;
|
||||||
|
--border-color: #E0E0E0;
|
||||||
|
|
||||||
|
--gradient: linear-gradient(45deg, var(--color-primary), var(--color-secondary));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg-color: #121212;
|
||||||
|
--surface-color: #1E1E1E;
|
||||||
|
--text-color: #E0E0E0;
|
||||||
|
--subtle-text-color: #adb5bd;
|
||||||
|
--border-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Reset & Base Styles --- */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-family-body);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
line-height: 1.6;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 calc(var(--base-spacing) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
font-family: var(--font-family-headings);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: 3rem; line-height: 1.2; }
|
||||||
|
h2 { font-size: 2.25rem; margin-bottom: var(--base-spacing); }
|
||||||
|
h3 { font-size: 1.5rem; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
a:hover { color: var(--color-secondary); }
|
||||||
|
|
||||||
|
/* --- Header --- */
|
||||||
|
.header {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||||
|
padding: var(--base-spacing) 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .header { box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
|
||||||
|
|
||||||
|
.header .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-family: var(--font-family-headings);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.logo:hover { color: var(--color-primary); }
|
||||||
|
|
||||||
|
.main-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: calc(var(--base-spacing) * 1.5);
|
||||||
|
}
|
||||||
|
.main-nav a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
.main-nav a.login-btn {
|
||||||
|
background: var(--gradient);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--base-spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Language Switcher --- */
|
||||||
|
.language-switcher {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.selected-lang {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
.selected-lang img {
|
||||||
|
width: 20px;
|
||||||
|
height: 15px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.selected-lang span {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.icon-sm {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.lang-dropdown {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
.language-switcher:hover .lang-dropdown {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.lang-dropdown a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: calc(var(--border-radius) / 2);
|
||||||
|
}
|
||||||
|
.lang-dropdown a:hover, .lang-dropdown a.active {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
.lang-dropdown a img {
|
||||||
|
width: 20px;
|
||||||
|
height: 15px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Theme Switch --- */
|
||||||
|
.theme-switch-wrapper { display: flex; align-items: center; }
|
||||||
|
.theme-switch { position: relative; display: inline-block; width: 50px; height: 26px; }
|
||||||
|
.theme-switch input { opacity: 0; width: 0; height: 0; }
|
||||||
|
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; }
|
||||||
|
.slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px; background-color: white; transition: .4s; }
|
||||||
|
input:checked + .slider { background-color: var(--color-primary); }
|
||||||
|
input:focus + .slider { box-shadow: 0 0 1px var(--color-primary); }
|
||||||
|
input:checked + .slider:before { transform: translateX(24px); }
|
||||||
|
.slider.round { border-radius: 34px; }
|
||||||
|
.slider.round:before { border-radius: 50%; }
|
||||||
|
|
||||||
|
/* --- Hero Section --- */
|
||||||
|
.hero {
|
||||||
|
text-align: center;
|
||||||
|
padding: calc(var(--base-spacing) * 5) 0;
|
||||||
|
background: var(--gradient);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.hero h1 { color: white; }
|
||||||
|
.hero p {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin: var(--base-spacing) 0 calc(var(--base-spacing) * 2);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.cta-button {
|
||||||
|
background: white;
|
||||||
|
color: var(--color-primary);
|
||||||
|
padding: 0.8rem 2.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: var(--font-family-headings);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.cta-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- How It Works Section --- */
|
||||||
|
.how-it-works {
|
||||||
|
padding: calc(var(--base-spacing) * 4) 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.steps {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
gap: calc(var(--base-spacing) * 2);
|
||||||
|
margin-top: calc(var(--base-spacing) * 2);
|
||||||
|
}
|
||||||
|
.step {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
.step-icon {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--gradient);
|
||||||
|
color: white;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: var(--base-spacing);
|
||||||
|
}
|
||||||
|
.step-icon i {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
.step h3 { margin-bottom: 0.5rem; }
|
||||||
|
.step p { color: var(--subtle-text-color); }
|
||||||
|
|
||||||
|
/* --- Generator Section --- */
|
||||||
|
.generator-section {
|
||||||
|
padding: calc(var(--base-spacing) * 4) 0;
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
.generator-section h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: calc(var(--base-spacing) * 2);
|
||||||
|
}
|
||||||
|
.generator-form {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: calc(var(--base-spacing) * 2);
|
||||||
|
}
|
||||||
|
.form-column-wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: var(--base-spacing);
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.form-group input[type="text"],
|
||||||
|
.form-group input[type="number"],
|
||||||
|
.form-group select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: var(--font-family-body);
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: border-color 0.2s, background-color 0.3s;
|
||||||
|
}
|
||||||
|
.form-group input:focus, .form-group select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.photo-upload-container .upload-area {
|
||||||
|
border: 2px dashed var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: calc(var(--base-spacing) * 2);
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
.photo-upload-container .upload-area:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.upload-area i {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
color: var(--subtle-text-color);
|
||||||
|
margin-bottom: var(--base-spacing);
|
||||||
|
}
|
||||||
|
.photo-preview {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--base-spacing);
|
||||||
|
margin-top: var(--base-spacing);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.preview-item {
|
||||||
|
position: relative;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
.preview-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
.remove-img-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -5px;
|
||||||
|
background: rgba(0,0,0,0.7);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.form-submit-group {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: calc(var(--base-spacing) * 2);
|
||||||
|
}
|
||||||
|
.form-submit-group .cta-button {
|
||||||
|
background: var(--gradient);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.form-submit-group .cta-button:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* --- Footer --- */
|
||||||
|
.footer {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
text-align: center;
|
||||||
|
padding: calc(var(--base-spacing) * 2) 0;
|
||||||
|
margin-top: calc(var(--base-spacing) * 3);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
color: var(--subtle-text-color);
|
||||||
|
transition: background-color 0.3s, border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Result & Error Styles --- */
|
||||||
|
.result-container {
|
||||||
|
margin-top: calc(var(--base-spacing) * 3);
|
||||||
|
padding: calc(var(--base-spacing) * 2);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-container h3 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: calc(var(--base-spacing) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item {
|
||||||
|
margin-bottom: var(--base-spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: var(--font-family-headings);
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item p {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
padding: var(--base-spacing);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-actions {
|
||||||
|
margin-top: calc(var(--base-spacing) * 1.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--base-spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: var(--font-family-headings);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.btn:hover { opacity: 0.85; }
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--gradient);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
margin-top: calc(var(--base-spacing) * 2);
|
||||||
|
padding: var(--base-spacing);
|
||||||
|
background-color: #ffcccc;
|
||||||
|
color: #990000;
|
||||||
|
border: 1px solid #ff9999;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .error-message {
|
||||||
|
background-color: #4d0000;
|
||||||
|
color: #ffc2c2;
|
||||||
|
border-color: #800000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- History Page --- */
|
||||||
|
.page-title {
|
||||||
|
text-align: center;
|
||||||
|
margin: calc(var(--base-spacing) * 2) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: calc(var(--base-spacing) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item .card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--subtle-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item .card-body {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden; /* Ensures children conform to border radius */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: calc(var(--base-spacing) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: var(--base-spacing) calc(var(--base-spacing) * 1.5);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Pricing Page --- */
|
||||||
|
.pricing-card-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card-title small {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h4 {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-unstyled {
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group .btn {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group .btn:not(:last-child) {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group .btn:not(:first-child) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- FAQ Page --- */
|
||||||
|
.accordion-button {
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: var(--font-family-headings);
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-button:not(.collapsed) {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-button:focus {
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(108, 99, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-item {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-body {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Terms Page --- */
|
||||||
|
.terms-content h4 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
1
assets/images/flags/de.svg
Normal file
1
assets/images/flags/de.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 3"><path d="M0 0h5v3H0z"/><path d="M0 1h5v2H0z" fill="#D00"/><path d="M0 2h5v1H0z" fill="#FFCE00"/></svg>
|
||||||
|
After Width: | Height: | Size: 160 B |
1
assets/images/flags/en.svg
Normal file
1
assets/images/flags/en.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30"><clipPath id="a"><path d="M0 0v30h60V0z"/></clipPath><clipPath id="b"><path d="M30 15h30v15zv15H0zH0V0h30z"/></clipPath><g clip-path="url(#a)"><path d="M0 0v30h60V0z" fill="#00247d"/><path d="M0 0l60 30m0-30L0 30" stroke="#fff" stroke-width="6"/><path d="M0 0l60 30m0-30L0 30" clip-path="url(#b)" stroke="#cf142b" stroke-width="4"/><path d="M30 0v30M0 15h60" stroke="#fff" stroke-width="10"/><path d="M30 0v30M0 15h60" stroke="#cf142b" stroke-width="6"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 524 B |
1
assets/images/flags/es.svg
Normal file
1
assets/images/flags/es.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><path d="M0 0h3v2H0z" fill="#c60b1e"/><path d="M0 .5h3v1H0z" fill="#ffc400"/></svg>
|
||||||
|
After Width: | Height: | Size: 141 B |
1
assets/images/flags/fr.svg
Normal file
1
assets/images/flags/fr.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><path d="M0 0h1v2H0z" fill="#0055a4"/><path d="M1 0h1v2H1z" fill="#fff"/><path d="M2 0h1v2H2z" fill="#ef4135"/></svg>
|
||||||
|
After Width: | Height: | Size: 175 B |
1
assets/images/flags/pl.svg
Normal file
1
assets/images/flags/pl.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 5"><path fill="#fff" d="M0 0h8v5H0z"/><path fill="#dc143c" d="M0 2.5h8V5H0z"/></svg>
|
||||||
|
After Width: | Height: | Size: 139 B |
130
assets/js/main.js
Normal file
130
assets/js/main.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// --- Feather Icons ---
|
||||||
|
feather.replace();
|
||||||
|
|
||||||
|
// --- Theme Switcher ---
|
||||||
|
const themeSwitch = document.getElementById('checkbox');
|
||||||
|
const currentTheme = localStorage.getItem('theme');
|
||||||
|
|
||||||
|
if (currentTheme) {
|
||||||
|
document.documentElement.setAttribute('data-theme', currentTheme);
|
||||||
|
if (currentTheme === 'dark') {
|
||||||
|
themeSwitch.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchTheme(e) {
|
||||||
|
if (e.target.checked) {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
|
localStorage.setItem('theme', 'dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-theme', 'light');
|
||||||
|
localStorage.setItem('theme', 'light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
themeSwitch.addEventListener('change', switchTheme, false);
|
||||||
|
|
||||||
|
// --- Generator Form: Photo Upload ---
|
||||||
|
const uploadArea = document.getElementById('upload-area');
|
||||||
|
const fileInput = document.getElementById('photo-upload');
|
||||||
|
const photoPreview = document.getElementById('photo-preview');
|
||||||
|
const MAX_FILES = 5;
|
||||||
|
let uploadedFiles = [];
|
||||||
|
|
||||||
|
if (uploadArea) {
|
||||||
|
uploadArea.addEventListener('click', () => fileInput.click());
|
||||||
|
|
||||||
|
uploadArea.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.style.borderColor = 'var(--color-primary)';
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener('dragleave', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.style.borderColor = 'var(--border-color)';
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.style.borderColor = 'var(--border-color)';
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
handleFiles(files);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
handleFiles(files);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFiles(files) {
|
||||||
|
for (const file of files) {
|
||||||
|
if (uploadedFiles.length < MAX_FILES && file.type.startsWith('image/')) {
|
||||||
|
uploadedFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreview() {
|
||||||
|
photoPreview.innerHTML = '';
|
||||||
|
uploadedFiles.forEach((file, index) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const previewItem = document.createElement('div');
|
||||||
|
previewItem.classList.add('preview-item');
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = e.target.result;
|
||||||
|
previewItem.appendChild(img);
|
||||||
|
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.classList.add('remove-img-btn');
|
||||||
|
removeBtn.innerHTML = '×';
|
||||||
|
removeBtn.addEventListener('click', () => {
|
||||||
|
uploadedFiles.splice(index, 1);
|
||||||
|
updatePreview();
|
||||||
|
});
|
||||||
|
previewItem.appendChild(removeBtn);
|
||||||
|
|
||||||
|
photoPreview.appendChild(previewItem);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Copy to Clipboard ---
|
||||||
|
const copyBtn = document.getElementById('copy-btn');
|
||||||
|
if(copyBtn) {
|
||||||
|
copyBtn.addEventListener('click', () => {
|
||||||
|
const title = document.getElementById('result-title')?.innerText || '';
|
||||||
|
const shortDesc = document.getElementById('result-short-desc')?.innerText || '';
|
||||||
|
const desc = document.getElementById('result-desc')?.innerText || '';
|
||||||
|
const measurements = document.getElementById('result-measurements')?.innerText || '';
|
||||||
|
const hashtags = document.getElementById('result-hashtags')?.innerText || '';
|
||||||
|
|
||||||
|
const fullText = `
|
||||||
|
${title}
|
||||||
|
|
||||||
|
${shortDesc}
|
||||||
|
|
||||||
|
${desc}
|
||||||
|
|
||||||
|
Measurements: ${measurements}
|
||||||
|
|
||||||
|
${hashtags}
|
||||||
|
`;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(fullText.trim()).then(() => {
|
||||||
|
const originalText = copyBtn.innerHTML;
|
||||||
|
copyBtn.innerHTML = 'Copied!';
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.innerHTML = originalText;
|
||||||
|
}, 2000);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to copy: ', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
30
db/setup.php
Normal file
30
db/setup.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$sql = "
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);";
|
||||||
|
$pdo->exec($sql);
|
||||||
|
echo "Table 'users' created successfully (if it didn't exist).\n";
|
||||||
|
|
||||||
|
$sql_generations = "
|
||||||
|
CREATE TABLE IF NOT EXISTS generations (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
prompt TEXT,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);";
|
||||||
|
$pdo->exec($sql_generations);
|
||||||
|
echo "Table 'generations' created successfully (if it didn't exist).\n";
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("DB ERROR: " . $e->getMessage());
|
||||||
|
}
|
||||||
66
faq.php
Normal file
66
faq.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'includes/translations.php';
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="container my-5">
|
||||||
|
<h1 class="text-center mb-5"><?php echo t('faq_title'); ?></h1>
|
||||||
|
|
||||||
|
<div class="accordion" id="faqAccordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingOne">
|
||||||
|
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||||
|
<?php echo t('faq_q1_title'); ?>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#faqAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<?php echo t('faq_q1_text'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingTwo">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
|
<?php echo t('faq_q2_title'); ?>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#faqAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<?php echo t('faq_q2_text'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingThree">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
||||||
|
<?php echo t('faq_q3_title'); ?>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#faqAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<?php echo t('faq_q3_text'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingFour">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
|
||||||
|
<?php echo t('faq_q4_title'); ?>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour" data-bs-parent="#faqAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<?php echo t('faq_q4_text'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php require_once 'includes/footer.php'; ?>
|
||||||
213
generate.php
Normal file
213
generate.php
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
require_once __DIR__ . '/includes/translations.php';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/ai/LocalAIApi.php';
|
||||||
|
|
||||||
|
// Set language from session or default to English
|
||||||
|
$lang = $_SESSION['lang'] ?? 'en';
|
||||||
|
|
||||||
|
// --- Helper Functions ---
|
||||||
|
|
||||||
|
function translate_to_english($text, $source_lang) {
|
||||||
|
if ($source_lang === 'en') {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
// In a real app, you would use a translation API.
|
||||||
|
// For this demo, we'll simulate by calling our AI with a translation prompt.
|
||||||
|
$prompt = "Translate the following text from '{$source_lang}' to English. Only return the translated text, with no extra commentary:
|
||||||
|
|
||||||
|
{$text}";
|
||||||
|
$response = LocalAIApi::createResponse([
|
||||||
|
'input' => [
|
||||||
|
['role' => 'system', 'content' => 'You are a translation assistant.'],
|
||||||
|
['role' => 'user', 'content' => $prompt],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($response['success'])) {
|
||||||
|
$decoded = LocalAIApi::decodeJsonFromResponse($response);
|
||||||
|
return $decoded['choices'][0]['message']['content'] ?? $text;
|
||||||
|
}
|
||||||
|
return $text; // Fallback to original text on error
|
||||||
|
}
|
||||||
|
|
||||||
|
function translate_from_english($text, $target_lang) {
|
||||||
|
if ($target_lang === 'en') {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
// Simulate translation
|
||||||
|
$prompt = "Translate the following text from English to '{$target_lang}'. Only return the translated text, with no extra commentary:
|
||||||
|
|
||||||
|
{$text}";
|
||||||
|
$response = LocalAIApi::createResponse([
|
||||||
|
'input' => [
|
||||||
|
['role' => 'system', 'content' => 'You are a translation assistant.'],
|
||||||
|
['role' => 'user', 'content' => $prompt],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($response['success'])) {
|
||||||
|
$decoded = LocalAIApi::decodeJsonFromResponse($response);
|
||||||
|
return $decoded['choices'][0]['message']['content'] ?? $text;
|
||||||
|
}
|
||||||
|
return $text; // Fallback to original text on error
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main Logic ---
|
||||||
|
|
||||||
|
$result_html = '';
|
||||||
|
$error_message = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// --- 1. Collect and Sanitize Inputs ---
|
||||||
|
$state = htmlspecialchars($_POST['state'] ?? '');
|
||||||
|
$size = htmlspecialchars($_POST['size'] ?? '');
|
||||||
|
$material = htmlspecialchars($_POST['material'] ?? '');
|
||||||
|
$brand = htmlspecialchars($_POST['brand'] ?? '');
|
||||||
|
$length = htmlspecialchars($_POST['length'] ?? '');
|
||||||
|
$chest_width = htmlspecialchars($_POST['chest_width'] ?? '');
|
||||||
|
$waist_width = htmlspecialchars($_POST['waist_width'] ?? '');
|
||||||
|
$hips_width = htmlspecialchars($_POST['hips_width'] ?? '');
|
||||||
|
$additional_info = htmlspecialchars($_POST['additional_info'] ?? '');
|
||||||
|
|
||||||
|
// --- 2. Validation ---
|
||||||
|
if (empty($state) || empty($size)) {
|
||||||
|
$_SESSION['generation_error'] = t('error_required_fields');
|
||||||
|
header('Location: index.php#generator');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Translation Pipeline (Input) ---
|
||||||
|
$state_en = translate_to_english($state, $lang);
|
||||||
|
$size_en = translate_to_english($size, $lang);
|
||||||
|
$material_en = translate_to_english($material, $lang);
|
||||||
|
$brand_en = translate_to_english($brand, $lang);
|
||||||
|
$additional_info_en = translate_to_english($additional_info, $lang);
|
||||||
|
|
||||||
|
// --- 4. Construct AI Prompt ---
|
||||||
|
$prompt_parts = [
|
||||||
|
"Generate a product listing for an online clothing store. The target audience is women aged 22-60.",
|
||||||
|
"The output must be a JSON object with the following keys: 'title', 'short_description', 'description', 'measurements', 'hashtags'.",
|
||||||
|
"The tone should be engaging, concise, and SEO-aware, incorporating trending keywords naturally.",
|
||||||
|
"Do not add any extra commentary or editorializing. Stick to the facts provided."
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($state_en === 'used') {
|
||||||
|
$prompt_parts[] = "Condition: Used (perfect condition).";
|
||||||
|
} elseif ($state_en === 'visibly_used') {
|
||||||
|
$prompt_parts[] = "Condition: Visibly used (may have some signs of use).";
|
||||||
|
} else {
|
||||||
|
$prompt_parts[] = "Condition: New.";
|
||||||
|
}
|
||||||
|
|
||||||
|
$prompt_parts[] = "Size: {$size_en}";
|
||||||
|
if (!empty($material_en)) $prompt_parts[] = "Material: {$material_en}";
|
||||||
|
if (!empty($brand_en)) $prompt_parts[] = "Brand: {$brand_en}";
|
||||||
|
|
||||||
|
$measurements_parts = [];
|
||||||
|
if (!empty($length)) $measurements_parts[] = "Length: {$length} cm";
|
||||||
|
if (!empty($chest_width)) $measurements_parts[] = "Chest width: {$chest_width} cm";
|
||||||
|
if (!empty($waist_width)) $measurements_parts[] = "Waist width: {$waist_width} cm";
|
||||||
|
if (!empty($hips_width)) $measurements_parts[] = "Hips width: {$hips_width} cm";
|
||||||
|
if (!empty($measurements_parts)) {
|
||||||
|
$prompt_parts[] = "Measurements: " . implode(', ', $measurements_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($additional_info_en)) {
|
||||||
|
$prompt_parts[] = "Additional Details: {$additional_info_en}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$prompt_parts[] = "Generate the output in English.";
|
||||||
|
$prompt = implode("
|
||||||
|
", $prompt_parts);
|
||||||
|
|
||||||
|
// --- 4. Call AI API ---
|
||||||
|
$ai_response = LocalAIApi::createResponse([
|
||||||
|
'input' => [
|
||||||
|
['role' => 'system', 'content' => 'You are an expert copywriter for e-commerce fashion brands.'],
|
||||||
|
['role' => 'user', 'content' => $prompt],
|
||||||
|
],
|
||||||
|
'model' => 'gpt-3.5-turbo', // Using a cost-effective model
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($ai_response['success'])) {
|
||||||
|
$decoded_response = LocalAIApi::decodeJsonFromResponse($ai_response);
|
||||||
|
$generated_text = $decoded_response['choices'][0]['message']['content'] ?? '';
|
||||||
|
|
||||||
|
// Find the JSON part of the response
|
||||||
|
$json_start = strpos($generated_text, '{');
|
||||||
|
$json_end = strrpos($generated_text, '}');
|
||||||
|
if ($json_start !== false && $json_end !== false) {
|
||||||
|
$json_str = substr($generated_text, $json_start, $json_end - $json_start + 1);
|
||||||
|
$output_data = json_decode($json_str, true);
|
||||||
|
|
||||||
|
if ($output_data && json_last_error() === JSON_ERROR_NONE) {
|
||||||
|
// --- 5. Translation Pipeline (Output) ---
|
||||||
|
$title = translate_from_english(htmlspecialchars($output_data['title'] ?? ''), $lang);
|
||||||
|
$short_desc = translate_from_english(htmlspecialchars($output_data['short_description'] ?? ''), $lang);
|
||||||
|
$description = nl2br(translate_from_english(htmlspecialchars($output_data['description'] ?? ''), $lang));
|
||||||
|
$measurements = translate_from_english(htmlspecialchars($output_data['measurements'] ?? ''), $lang);
|
||||||
|
$hashtags = translate_from_english(htmlspecialchars($output_data['hashtags'] ?? ''), $lang);
|
||||||
|
|
||||||
|
// --- 6. Save to DB if user is logged in ---
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO generations (user_id, prompt, description) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$_SESSION['user_id'], $prompt, $json_str]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Optional: log error to a file
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 7. Format Output ---
|
||||||
|
$result_html = "
|
||||||
|
<div class='result-container'>
|
||||||
|
<h3>" . t('generated_result') . "</h3>
|
||||||
|
<div class='result-item'>
|
||||||
|
<label>" . t('output_title') . "</label>
|
||||||
|
<p id='result-title'>{$title}</p>
|
||||||
|
</div>
|
||||||
|
<div class='result-item'>
|
||||||
|
<label>" . t('output_short_description') . "</label>
|
||||||
|
<p id='result-short-desc'>{$short_desc}</p>
|
||||||
|
</div>
|
||||||
|
<div class='result-item'>
|
||||||
|
<label>" . t('output_description') . "</label>
|
||||||
|
<p id='result-desc'>{$description}</p>
|
||||||
|
</div>
|
||||||
|
<div class='result-item'>
|
||||||
|
<label>" . t('output_measurements') . "</label>
|
||||||
|
<p id='result-measurements'>{$measurements}</p>
|
||||||
|
</div>
|
||||||
|
<div class='result-item'>
|
||||||
|
<label>" . t('output_hashtags') . "</label>
|
||||||
|
<p id='result-hashtags'>{$hashtags}</p>
|
||||||
|
</div>
|
||||||
|
<div class='result-actions'>
|
||||||
|
<button class='btn btn-secondary' id='copy-btn'><i class='ph ph-copy'></i> " . t('copy_to_clipboard') . "</button>
|
||||||
|
<button class='btn btn-primary' id='approve-btn'><i class='ph ph-check'></i> " . t('approve') . "</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
$error_message = t('error_parsing_ai_response');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error_message = t('error_parsing_ai_response');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error_message = t('error_calling_ai_api') . ': ' . htmlspecialchars($ai_response['error'] ?? 'Unknown error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store result in session to display on the main page
|
||||||
|
$_SESSION['generation_result'] = $result_html;
|
||||||
|
$_SESSION['generation_error'] = $error_message;
|
||||||
|
|
||||||
|
// Redirect back to the main page
|
||||||
|
header('Location: index.php#generator');
|
||||||
|
exit();
|
||||||
105
history.php
Normal file
105
history.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
require_once __DIR__ . '/ai/LocalAIApi.php';
|
||||||
|
|
||||||
|
// Redirect to login if not logged in
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for translation
|
||||||
|
function translate_from_english($text, $target_lang) {
|
||||||
|
if ($target_lang === 'en') {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
// Simulate translation
|
||||||
|
$prompt = "Translate the following text from English to '{$target_lang}'. Only return the translated text, with no extra commentary:\n\n{$text}";
|
||||||
|
$response = LocalAIApi::createResponse([
|
||||||
|
'input' => [
|
||||||
|
['role' => 'system', 'content' => 'You are a translation assistant.'],
|
||||||
|
['role' => 'user', 'content' => $prompt],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($response['success'])) {
|
||||||
|
$decoded = LocalAIApi::decodeJsonFromResponse($response);
|
||||||
|
return $decoded['choices'][0]['message']['content'] ?? $text;
|
||||||
|
}
|
||||||
|
return $text; // Fallback to original text on error
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
// Fetch history from the database
|
||||||
|
$generations = [];
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM generations WHERE user_id = ? ORDER BY created_at DESC");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$generations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Optional: handle error
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<h1 class="page-title"><?php echo t('history_title'); ?></h1>
|
||||||
|
|
||||||
|
<?php if (empty($generations)): ?>
|
||||||
|
<div class="card text-center">
|
||||||
|
<div class="card-body">
|
||||||
|
<p><?php echo t('history_empty'); ?></p>
|
||||||
|
<a href="index.php#generator" class="btn btn-primary"><?php echo t('history_generate_now'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="history-list">
|
||||||
|
<?php foreach ($generations as $index => $generation): ?>
|
||||||
|
<?php
|
||||||
|
$output_data = json_decode($generation['description'], true);
|
||||||
|
if (!$output_data) continue; // Skip if JSON is invalid
|
||||||
|
|
||||||
|
// Translate the fields for display
|
||||||
|
$title = translate_from_english(htmlspecialchars($output_data['title'] ?? ''), $lang);
|
||||||
|
$short_desc = translate_from_english(htmlspecialchars($output_data['short_description'] ?? ''), $lang);
|
||||||
|
$description = nl2br(translate_from_english(htmlspecialchars($output_data['description'] ?? ''), $lang));
|
||||||
|
$measurements = translate_from_english(htmlspecialchars($output_data['measurements'] ?? ''), $lang);
|
||||||
|
$hashtags = translate_from_english(htmlspecialchars($output_data['hashtags'] ?? ''), $lang);
|
||||||
|
?>
|
||||||
|
<div class="card history-item">
|
||||||
|
<div class="card-header">
|
||||||
|
<span><?php echo t('generation_date'); ?>: <?php echo date('F j, Y, g:i a', strtotime($generation['created_at'])); ?></span>
|
||||||
|
<button class="btn btn-sm btn-secondary copy-btn" data-clipboard-target="#history-content-<?php echo $index; ?>"><i class="ph ph-copy"></i> <?php echo t('copy_to_clipboard'); ?></button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="history-content-<?php echo $index; ?>">
|
||||||
|
<div class="result-item">
|
||||||
|
<label><?php echo t('output_title'); ?></label>
|
||||||
|
<p><?php echo $title; ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label><?php echo t('output_short_description'); ?></label>
|
||||||
|
<p><?php echo $short_desc; ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label><?php echo t('output_description'); ?></label>
|
||||||
|
<p><?php echo $description; ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label><?php echo t('output_measurements'); ?></label>
|
||||||
|
<p><?php echo $measurements; ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-item">
|
||||||
|
<label><?php echo t('output_hashtags'); ?></label>
|
||||||
|
<p><?php echo $hashtags; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include __DIR__ . '/includes/footer.php'; ?>
|
||||||
5
includes/footer.php
Normal file
5
includes/footer.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<p><?= t('footer_copyright') ?></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
95
includes/header.php
Normal file
95
includes/header.php
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() == PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
require_once __DIR__ . '/translations.php';
|
||||||
|
|
||||||
|
// Handle language switching
|
||||||
|
if (isset($_GET['lang'])) {
|
||||||
|
$_SESSION['lang'] = $_GET['lang'];
|
||||||
|
// Remove the lang parameter from the URL
|
||||||
|
$url = strtok($_SERVER["REQUEST_URI"], '?');
|
||||||
|
// Re-add other query parameters if they exist
|
||||||
|
$query = parse_url($_SERVER["REQUEST_URI"], PHP_URL_QUERY);
|
||||||
|
if ($query) {
|
||||||
|
parse_str($query, $params);
|
||||||
|
unset($params['lang']);
|
||||||
|
if (!empty($params)) {
|
||||||
|
$url .= '?' . http_build_query($params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header('Location: ' . $url);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$lang = $_SESSION['lang'] ?? 'en';
|
||||||
|
$translations = get_translations();
|
||||||
|
|
||||||
|
function t($key) {
|
||||||
|
global $translations, $lang;
|
||||||
|
return $translations[$lang][$key] ?? $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_logged_in = isset($_SESSION['user_id']);
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<?php echo $lang; ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo t('app_name'); ?></title>
|
||||||
|
<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=Poppins:wght@400;600;700&family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
<script src="https://unpkg.com/phosphor-icons"></script>
|
||||||
|
</head>
|
||||||
|
<body data-theme="light">
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<a href="index.php" class="logo"><?php echo t('app_name'); ?></a>
|
||||||
|
|
||||||
|
<nav class="main-nav">
|
||||||
|
<a href="index.php#generator"><?php echo t('nav_generator'); ?></a>
|
||||||
|
<a href="pricing.php"><?php echo t('nav_pricing'); ?></a>
|
||||||
|
<a href="faq.php"><?php echo t('nav_faq'); ?></a>
|
||||||
|
<a href="terms.php"><?php echo t('nav_terms'); ?></a>
|
||||||
|
<?php if ($is_logged_in): ?>
|
||||||
|
<a href="history.php"><?php echo t('nav_history'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="header-actions">
|
||||||
|
<div class="language-switcher">
|
||||||
|
<div class="selected-lang">
|
||||||
|
<img src="assets/images/flags/<?php echo $lang; ?>.svg" alt="<?php echo $lang; ?>">
|
||||||
|
<i class="ph-bold ph-caret-down icon-sm"></i>
|
||||||
|
</div>
|
||||||
|
<div class="lang-dropdown">
|
||||||
|
<?php foreach (array_keys($translations) as $lang_code): ?>
|
||||||
|
<a href="?lang=<?php echo $lang_code; ?>" class="<?php echo $lang === $lang_code ? 'active' : ''; ?>">
|
||||||
|
<img src="assets/images/flags/<?php echo $lang_code; ?>.svg" alt="<?php echo $lang_code; ?>">
|
||||||
|
<span><?php echo strtoupper($lang_code); ?></span>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="theme-switch-wrapper">
|
||||||
|
<label class="theme-switch" for="theme-checkbox">
|
||||||
|
<input type="checkbox" id="theme-checkbox" />
|
||||||
|
<div class="slider round"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($is_logged_in): ?>
|
||||||
|
<a href="profile.php" class="nav-link"><?php echo t('nav_profile'); ?></a>
|
||||||
|
<a href="logout.php" class="nav-link"><?php echo t('nav_logout'); ?></a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="register.php" class="nav-link"><?php echo t('nav_register'); ?></a>
|
||||||
|
<a href="login.php" class="login-btn"><?php echo t('nav_login'); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
674
includes/translations.php
Normal file
674
includes/translations.php
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
<?php
|
||||||
|
function get_translations() {
|
||||||
|
return [
|
||||||
|
'en' => [
|
||||||
|
'app_name' => 'Captionista',
|
||||||
|
'nav_generator' => 'Generator',
|
||||||
|
'nav_history' => 'History',
|
||||||
|
'nav_subscription' => 'Subscription',
|
||||||
|
'nav_login' => 'Login',
|
||||||
|
'nav_register' => 'Register',
|
||||||
|
'nav_logout' => 'Logout',
|
||||||
|
'nav_profile' => 'Profile',
|
||||||
|
'hero_title' => 'AI-Powered Descriptions for Your Clothing Store.',
|
||||||
|
'hero_subtitle' => 'Effortlessly create compelling, SEO-friendly product listings in seconds.',
|
||||||
|
'hero_cta' => 'Get Started for Free',
|
||||||
|
'how_it_works_title' => 'How It Works',
|
||||||
|
'step1_title' => 'Upload',
|
||||||
|
'step1_desc' => 'Upload up to 5 photos of your product.',
|
||||||
|
'step2_title' => 'Input Details',
|
||||||
|
'step2_desc' => 'Add key information like material, size, and brand.',
|
||||||
|
'step3_title' => 'Generate',
|
||||||
|
'step3_desc' => 'Receive a complete, ready-to-use description.',
|
||||||
|
'features_title' => 'Features',
|
||||||
|
'feature1_title' => 'AI-Powered Descriptions',
|
||||||
|
'feature2_title' => 'Multilingual Support',
|
||||||
|
'feature3_title' => 'History & Export',
|
||||||
|
'pricing_title' => 'Pricing',
|
||||||
|
'pricing_free' => 'Free',
|
||||||
|
'pricing_paid' => 'Paid',
|
||||||
|
'pricing_premium' => 'Premium',
|
||||||
|
'pricing_coming_soon' => 'Coming Soon',
|
||||||
|
'footer_copyright' => '© 2025 Captionista. All Rights Reserved.',
|
||||||
|
'generator_form_title' => 'Create Your Description',
|
||||||
|
'upload_label' => 'Upload Photos (up to 5)',
|
||||||
|
'state_label' => 'State',
|
||||||
|
'state_new' => 'New',
|
||||||
|
'state_used' => 'Used',
|
||||||
|
'state_visibly_used' => 'Visibly Used',
|
||||||
|
'length_label' => 'Length (cm)',
|
||||||
|
'width_chest_label' => 'Chest Width (cm)',
|
||||||
|
'width_waist_label' => 'Waist Width (cm)',
|
||||||
|
'width_hips_label' => 'Hips Width (cm)',
|
||||||
|
'material_label' => 'Material',
|
||||||
|
'size_label' => 'Size',
|
||||||
|
'brand_label' => 'Brand/Producer',
|
||||||
|
'additional_info_label' => 'Additional Info',
|
||||||
|
'additional_info_placeholder' => 'e.g., small stain on the sleeve, missing button',
|
||||||
|
'generate_button' => 'Generate Description',
|
||||||
|
'generated_result' => 'Generated Result',
|
||||||
|
'output_title' => 'Title',
|
||||||
|
'output_short_description' => 'Short Description',
|
||||||
|
'output_description' => 'Description',
|
||||||
|
'output_measurements' => 'Measurements',
|
||||||
|
'output_hashtags' => 'Hashtags',
|
||||||
|
'copy_to_clipboard' => 'Copy',
|
||||||
|
'approve' => 'Approve',
|
||||||
|
'error_parsing_ai_response' => 'Error: Could not understand the AI response. Please try again.',
|
||||||
|
'error_calling_ai_api' => 'Error: Could not connect to the AI service. Please try again later.',
|
||||||
|
'error_required_fields' => 'Error: Please fill in all required fields (State and Size).',
|
||||||
|
'register_title' => 'Register',
|
||||||
|
'create_account' => 'Create Your Account',
|
||||||
|
'register_subtitle' => 'Join Captionista to start generating descriptions.',
|
||||||
|
'email' => 'Email',
|
||||||
|
'password' => 'Password',
|
||||||
|
'confirm_password' => 'Confirm Password',
|
||||||
|
'register_button' => 'Register',
|
||||||
|
'already_have_account' => 'Already have an account?',
|
||||||
|
'login_link' => 'Login',
|
||||||
|
'login_title' => 'Login',
|
||||||
|
'login_heading' => 'Welcome Back!',
|
||||||
|
'login_subtitle' => 'Log in to access your account.',
|
||||||
|
'login_button' => 'Login',
|
||||||
|
'no_account' => "Don't have an account?",
|
||||||
|
'register_link' => 'Register',
|
||||||
|
'fill_all_fields' => 'Please fill in all fields.',
|
||||||
|
'passwords_do_not_match' => 'Passwords do not match.',
|
||||||
|
'invalid_email' => 'Invalid email format.',
|
||||||
|
'email_already_registered' => 'This email is already registered.',
|
||||||
|
'registration_successful' => 'Registration successful! You can now log in.',
|
||||||
|
'login_now' => 'Login now.',
|
||||||
|
'invalid_credentials' => 'Invalid email or password.',
|
||||||
|
'profile_link' => 'Profile',
|
||||||
|
'profile_heading' => 'Your Profile',
|
||||||
|
'profile_subtitle' => 'Update your account details.',
|
||||||
|
'profile_updated_successfully' => 'Profile updated successfully.',
|
||||||
|
'email_cannot_be_changed' => 'Email cannot be changed.',
|
||||||
|
'new_password' => 'New Password',
|
||||||
|
'confirm_new_password' => 'Confirm New Password',
|
||||||
|
'update_profile_button' => 'Update Profile',
|
||||||
|
'history_title' => 'Generation History',
|
||||||
|
'history_empty' => "You haven't generated any descriptions yet.",
|
||||||
|
'history_generate_now' => 'Generate Your First Description',
|
||||||
|
'generation_date' => 'Generated on',
|
||||||
|
'nav_pricing' => 'Pricing',
|
||||||
|
'nav_faq' => 'FAQ',
|
||||||
|
'nav_terms' => 'Terms of Use',
|
||||||
|
'pricing_subtitle' => 'Choose the plan that works for you.',
|
||||||
|
'monthly' => 'Monthly',
|
||||||
|
'annually' => 'Annually',
|
||||||
|
'save_15_percent' => 'Save 15%',
|
||||||
|
'basic_plan_name' => 'Basic',
|
||||||
|
'premium_plan_name' => 'Premium',
|
||||||
|
'basic_plan_limit' => '150 generations/month',
|
||||||
|
'premium_plan_limit' => 'Unlimited generations',
|
||||||
|
'feature_150_generations' => '150 generations per month',
|
||||||
|
'feature_unlimited_generations' => 'Unlimited generations',
|
||||||
|
'feature_standard_support' => 'Standard email support',
|
||||||
|
'feature_priority_support' => 'Priority email support',
|
||||||
|
'feature_history_access' => 'Access to generation history',
|
||||||
|
'choose_plan' => 'Choose Plan',
|
||||||
|
'month_short' => 'mo',
|
||||||
|
'billed_annually_at' => 'Billed annually at',
|
||||||
|
'pricing_footer_text' => 'You can upgrade, downgrade, or cancel your subscription at any time. No refunds for partial periods.',
|
||||||
|
'faq_title' => 'Frequently Asked Questions',
|
||||||
|
'faq_q1_title' => 'How does the subscription work?',
|
||||||
|
'faq_q1_text' => 'You can choose between a monthly or annual subscription. The annual plan gives you a 15% discount. Your subscription will automatically renew at the end of each billing cycle.',
|
||||||
|
'faq_q2_title' => 'Can I cancel my subscription?',
|
||||||
|
'faq_q2_text' => 'Yes, you can cancel your subscription at any time. If you cancel, your subscription will remain active until the end of the current billing period, and it will not be renewed. We do not offer refunds for partial subscription periods.',
|
||||||
|
'faq_q3_title' => 'Can I change my plan?',
|
||||||
|
'faq_q3_text' => 'Yes, you can upgrade or downgrade your plan at any time. The changes will take effect in the next billing cycle.',
|
||||||
|
'faq_q4_title' => 'What happens if I exceed my generation limit on the Basic plan?',
|
||||||
|
'faq_q4_text' => 'If you reach the 150-generation limit on the Basic plan, you will need to upgrade to the Premium plan to continue generating descriptions within the same month.',
|
||||||
|
'terms_title' => 'Terms of Use',
|
||||||
|
'terms_last_updated' => 'Last Updated: November 9, 2025',
|
||||||
|
'terms_section1_title' => '1. Acceptance of Terms',
|
||||||
|
'terms_section1_text' => 'By accessing and using our service, you accept and agree to be bound by the terms and provision of this agreement. In addition, when using these particular services, you shall be subject to any posted guidelines or rules applicable to such services.',
|
||||||
|
'terms_section2_title' => '2. User Accounts',
|
||||||
|
'terms_section2_text' => 'You are responsible for maintaining the confidentiality of your account and password. You agree to accept responsibility for all activities that occur under your account or password.',
|
||||||
|
'terms_section3_title' => '3. Subscription and Payment',
|
||||||
|
'terms_section3_text' => 'All subscriptions are billed in advance on a monthly or annual basis and are non-refundable. Your subscription will automatically renew unless you cancel it.',
|
||||||
|
'terms_section4_title' => '4. Termination',
|
||||||
|
'terms_section4_text' => 'We may terminate or suspend your account and bar access to the service immediately, without prior notice or liability, under our sole discretion, for any reason whatsoever and without limitation, including but not limited to a breach of the Terms.',
|
||||||
|
'terms_section5_title' => '5. Changes to Terms',
|
||||||
|
'terms_section5_text' => 'We reserve the right, at our sole discretion, to modify or replace these Terms at any time. We will provide at least 30 days notice prior to any new terms taking effect.',
|
||||||
|
],
|
||||||
|
'pl' => [
|
||||||
|
'app_name' => 'Captionista',
|
||||||
|
'nav_generator' => 'Generator',
|
||||||
|
'nav_history' => 'Historia',
|
||||||
|
'nav_subscription' => 'Subskrypcja',
|
||||||
|
'nav_login' => 'Zaloguj się',
|
||||||
|
'nav_register' => 'Zarejestruj się',
|
||||||
|
'nav_logout' => 'Wyloguj się',
|
||||||
|
'nav_profile' => 'Profil',
|
||||||
|
'hero_title' => 'Opisy do Twojego sklepu odzieżowego wspierane przez AI.',
|
||||||
|
'hero_subtitle' => 'Twórz atrakcyjne, zoptymalizowane pod SEO opisy produktów w kilka sekund.',
|
||||||
|
'hero_cta' => 'Zacznij za darmo',
|
||||||
|
'how_it_works_title' => 'Jak to działa',
|
||||||
|
'step1_title' => 'Prześlij',
|
||||||
|
'step1_desc' => 'Prześlij do 5 zdjęć swojego produktu.',
|
||||||
|
'step2_title' => 'Wprowadź dane',
|
||||||
|
'step2_desc' => 'Dodaj kluczowe informacje, takie jak materiał, rozmiar i marka.',
|
||||||
|
'step3_title' => 'Generuj',
|
||||||
|
'step3_desc' => 'Otrzymaj kompletny, gotowy do użycia opis.',
|
||||||
|
'features_title' => 'Funkcje',
|
||||||
|
'feature1_title' => 'Opisy wspierane przez AI',
|
||||||
|
'feature2_title' => 'Wsparcie wielojęzyczne',
|
||||||
|
'feature3_title' => 'Historia i eksport',
|
||||||
|
'pricing_title' => 'Cennik',
|
||||||
|
'pricing_free' => 'Darmowy',
|
||||||
|
'pricing_paid' => 'Płatny',
|
||||||
|
'pricing_premium' => 'Premium',
|
||||||
|
'pricing_coming_soon' => 'Wkrótce',
|
||||||
|
'footer_copyright' => '© 2025 Captionista. Wszelkie prawa zastrzeżone.',
|
||||||
|
'generator_form_title' => 'Stwórz swój opis',
|
||||||
|
'upload_label' => 'Prześlij zdjęcia (do 5)',
|
||||||
|
'state_label' => 'Stan',
|
||||||
|
'state_new' => 'Nowy',
|
||||||
|
'state_used' => 'Używany',
|
||||||
|
'state_visibly_used' => 'Widoczne ślady użytkowania',
|
||||||
|
'length_label' => 'Długość (cm)',
|
||||||
|
'width_chest_label' => 'Szerokość w klatce piersiowej (cm)',
|
||||||
|
'width_waist_label' => 'Szerokość w talii (cm)',
|
||||||
|
'width_hips_label' => 'Szerokość w biodrach (cm)',
|
||||||
|
'material_label' => 'Materiał',
|
||||||
|
'size_label' => 'Rozmiar',
|
||||||
|
'brand_label' => 'Marka/Producent',
|
||||||
|
'additional_info_label' => 'Dodatkowe informacje',
|
||||||
|
'additional_info_placeholder' => 'np. mała plama na rękawie, brak guzika',
|
||||||
|
'generate_button' => 'Generuj opis',
|
||||||
|
'generated_result' => 'Wygenerowany wynik',
|
||||||
|
'output_title' => 'Tytuł',
|
||||||
|
'output_short_description' => 'Krótki opis',
|
||||||
|
'output_description' => 'Opis',
|
||||||
|
'output_measurements' => 'Wymiary',
|
||||||
|
'output_hashtags' => 'Hashtagi',
|
||||||
|
'copy_to_clipboard' => 'Kopiuj',
|
||||||
|
'approve' => 'Zatwierdź',
|
||||||
|
'error_parsing_ai_response' => 'Błąd: Nie można zinterpretować odpowiedzi AI. Proszę spróbować ponownie.',
|
||||||
|
'error_calling_ai_api' => 'Błąd: Nie można połączyć się z usługą AI. Proszę spróbować później.',
|
||||||
|
'error_required_fields' => 'Błąd: Proszę wypełnić wszystkie wymagane pola (Stan i Rozmiar).',
|
||||||
|
'register_title' => 'Rejestracja',
|
||||||
|
'create_account' => 'Stwórz swoje konto',
|
||||||
|
'register_subtitle' => 'Dołącz do Captionista, aby zacząć generować opisy.',
|
||||||
|
'email' => 'Email',
|
||||||
|
'password' => 'Hasło',
|
||||||
|
'confirm_password' => 'Potwierdź hasło',
|
||||||
|
'register_button' => 'Zarejestruj się',
|
||||||
|
'already_have_account' => 'Masz już konto?',
|
||||||
|
'login_link' => 'Zaloguj się',
|
||||||
|
'login_title' => 'Logowanie',
|
||||||
|
'login_heading' => 'Witaj z powrotem!',
|
||||||
|
'login_subtitle' => 'Zaloguj się, aby uzyskać dostęp do swojego konta.',
|
||||||
|
'login_button' => 'Zaloguj się',
|
||||||
|
'no_account' => 'Nie masz konta?',
|
||||||
|
'register_link' => 'Zarejestruj się',
|
||||||
|
'fill_all_fields' => 'Proszę wypełnić wszystkie pola.',
|
||||||
|
'passwords_do_not_match' => 'Hasła nie są zgodne.',
|
||||||
|
'invalid_email' => 'Nieprawidłowy format email.',
|
||||||
|
'email_already_registered' => 'Ten email jest już zarejestrowany.',
|
||||||
|
'registration_successful' => 'Rejestracja pomyślna! Możesz się teraz zalogować.',
|
||||||
|
'login_now' => 'Zaloguj się teraz.',
|
||||||
|
'invalid_credentials' => 'Nieprawidłowy email lub hasło.',
|
||||||
|
'profile_link' => 'Profil',
|
||||||
|
'profile_heading' => 'Twój profil',
|
||||||
|
'profile_subtitle' => 'Zaktualizuj dane swojego konta.',
|
||||||
|
'profile_updated_successfully' => 'Profil zaktualizowany pomyślnie.',
|
||||||
|
'email_cannot_be_changed' => 'Email nie może zostać zmieniony.',
|
||||||
|
'new_password' => 'Nowe hasło',
|
||||||
|
'confirm_new_password' => 'Potwierdź nowe hasło',
|
||||||
|
'update_profile_button' => 'Zaktualizuj profil',
|
||||||
|
'history_title' => 'Historia generacji',
|
||||||
|
'history_empty' => 'Nie wygenerowałeś jeszcze żadnych opisów.',
|
||||||
|
'history_generate_now' => 'Wygeneruj swój pierwszy opis',
|
||||||
|
'generation_date' => 'Wygenerowano',
|
||||||
|
'nav_pricing' => 'Cennik',
|
||||||
|
'nav_faq' => 'FAQ',
|
||||||
|
'nav_terms' => 'Regulamin',
|
||||||
|
'pricing_subtitle' => 'Wybierz plan, który Ci odpowiada.',
|
||||||
|
'monthly' => 'Miesięcznie',
|
||||||
|
'annually' => 'Rocznie',
|
||||||
|
'save_15_percent' => 'Oszczędź 15%',
|
||||||
|
'basic_plan_name' => 'Podstawowy',
|
||||||
|
'premium_plan_name' => 'Premium',
|
||||||
|
'basic_plan_limit' => '150 generacji/miesiąc',
|
||||||
|
'premium_plan_limit' => 'Nielimitowane generacje',
|
||||||
|
'feature_150_generations' => '150 generacji miesięcznie',
|
||||||
|
'feature_unlimited_generations' => 'Nielimitowane generacje',
|
||||||
|
'feature_standard_support' => 'Standardowe wsparcie email',
|
||||||
|
'feature_priority_support' => 'Priorytetowe wsparcie email',
|
||||||
|
'feature_history_access' => 'Dostęp do historii generacji',
|
||||||
|
'choose_plan' => 'Wybierz plan',
|
||||||
|
'month_short' => 'mies',
|
||||||
|
'billed_annually_at' => 'Płatne rocznie w kwocie',
|
||||||
|
'pricing_footer_text' => 'Możesz zmieniać, anulować lub ulepszać swoją subskrypcję w dowolnym momencie. Brak zwrotów za częściowe okresy.',
|
||||||
|
'faq_title' => 'Często zadawane pytania',
|
||||||
|
'faq_q1_title' => 'Jak działa subskrypcja?',
|
||||||
|
'faq_q1_text' => 'Możesz wybrać subskrypcję miesięczną lub roczną. Plan roczny daje 15% zniżki. Twoja subskrypcja odnowi się automatycznie na koniec każdego cyklu rozliczeniowego.',
|
||||||
|
'faq_q2_title' => 'Czy mogę anulować subskrypcję?',
|
||||||
|
'faq_q2_text' => 'Tak, możesz anulować subskrypcję w dowolnym momencie. Jeśli anulujesz, subskrypcja pozostanie aktywna do końca bieżącego okresu rozliczeniowego i nie zostanie odnowiona. Nie oferujemy zwrotów za częściowe okresy subskrypcji.',
|
||||||
|
'faq_q3_title' => 'Czy mogę zmienić plan?',
|
||||||
|
'faq_q3_text' => 'Tak, możesz w dowolnym momencie zmienić plan na wyższy lub niższy. Zmiany wejdą w życie w następnym cyklu rozliczeniowym.',
|
||||||
|
'faq_q4_title' => 'Co się stanie, jeśli przekroczę limit generacji w planie Podstawowym?',
|
||||||
|
'faq_q4_text' => 'Jeśli osiągniesz limit 150 generacji w planie Podstawowym, będziesz musiał przejść na plan Premium, aby kontynuować generowanie opisów w tym samym miesiącu.',
|
||||||
|
'terms_title' => 'Regulamin',
|
||||||
|
'terms_last_updated' => 'Ostatnia aktualizacja: 9 listopada 2025',
|
||||||
|
'terms_section1_title' => '1. Akceptacja warunków',
|
||||||
|
'terms_section1_text' => 'Korzystając z naszej usługi, akceptujesz i zgadzasz się na przestrzeganie warunków niniejszej umowy. Ponadto, korzystając z tych usług, podlegasz wszelkim opublikowanym wytycznym lub zasadom mającym zastosowanie do takich usług.',
|
||||||
|
'terms_section2_title' => '2. Konta użytkowników',
|
||||||
|
'terms_section2_text' => 'Jesteś odpowiedzialny za zachowanie poufności swojego konta i hasła. Zgadzasz się przyjąć odpowiedzialność za wszystkie działania, które mają miejsce na Twoim koncie lub pod Twoim hasłem.',
|
||||||
|
'terms_section3_title' => '3. Subskrypcja i płatności',
|
||||||
|
'terms_section3_text' => 'Wszystkie subskrypcje są opłacane z góry miesięcznie lub rocznie i nie podlegają zwrotowi. Twoja subskrypcja zostanie automatycznie odnowiona, chyba że ją anulujesz.',
|
||||||
|
'terms_section4_title' => '4. Zakończenie',
|
||||||
|
'terms_section4_text' => 'Możemy zamknąć lub zawiesić Twoje konto i zablokować dostęp do usługi natychmiast, bez uprzedniego powiadomienia lub odpowiedzialności, według naszego wyłącznego uznania, z dowolnego powodu i bez ograniczeń, w tym między innymi z powodu naruszenia Warunków.',
|
||||||
|
'terms_section5_title' => '5. Zmiany w Regulaminie',
|
||||||
|
'terms_section5_text' => 'Zastrzegamy sobie prawo, według naszego wyłącznego uznania, do modyfikowania lub zastępowania niniejszych Warunków w dowolnym momencie. Powiadomimy o wszelkich nowych warunkach co najmniej 30 dni przed ich wejściem w życie.',
|
||||||
|
],
|
||||||
|
'es' => [
|
||||||
|
'app_name' => 'Captionista',
|
||||||
|
'nav_generator' => 'Generador',
|
||||||
|
'nav_history' => 'Historial',
|
||||||
|
'nav_subscription' => 'Suscripción',
|
||||||
|
'nav_login' => 'Iniciar sesión',
|
||||||
|
'nav_register' => 'Registrarse',
|
||||||
|
'nav_logout' => 'Cerrar sesión',
|
||||||
|
'nav_profile' => 'Perfil',
|
||||||
|
'hero_title' => 'Descripciones para tu tienda de ropa impulsadas por IA.',
|
||||||
|
'hero_subtitle' => 'Crea descripciones de productos atractivas y optimizadas para SEO en segundos.',
|
||||||
|
'hero_cta' => 'Empieza gratis',
|
||||||
|
'how_it_works_title' => 'Cómo funciona',
|
||||||
|
'step1_title' => 'Subir',
|
||||||
|
'step1_desc' => 'Sube hasta 5 fotos de tu producto.',
|
||||||
|
'step2_title' => 'Ingresar detalles',
|
||||||
|
'step2_desc' => 'Añade información clave como material, talla y marca.',
|
||||||
|
'step3_title' => 'Generar',
|
||||||
|
'step3_desc' => 'Recibe una descripción completa y lista para usar.',
|
||||||
|
'features_title' => 'Características',
|
||||||
|
'feature1_title' => 'Descripciones impulsadas por IA',
|
||||||
|
'feature2_title' => 'Soporte multilingüe',
|
||||||
|
'feature3_title' => 'Historial y exportación',
|
||||||
|
'pricing_title' => 'Precios',
|
||||||
|
'pricing_free' => 'Gratis',
|
||||||
|
'pricing_paid' => 'Pagado',
|
||||||
|
'pricing_premium' => 'Premium',
|
||||||
|
'pricing_coming_soon' => 'Próximamente',
|
||||||
|
'footer_copyright' => '© 2025 Captionista. Todos los derechos reservados.',
|
||||||
|
'generator_form_title' => 'Crea tu descripción',
|
||||||
|
'upload_label' => 'Subir fotos (hasta 5)',
|
||||||
|
'state_label' => 'Estado',
|
||||||
|
'state_new' => 'Nuevo',
|
||||||
|
'state_used' => 'Usado',
|
||||||
|
'state_visibly_used' => 'Visiblemente usado',
|
||||||
|
'length_label' => 'Largo (cm)',
|
||||||
|
'width_chest_label' => 'Ancho de pecho (cm)',
|
||||||
|
'width_waist_label' => 'Ancho de cintura (cm)',
|
||||||
|
'width_hips_label' => 'Ancho de caderas (cm)',
|
||||||
|
'material_label' => 'Material',
|
||||||
|
'size_label' => 'Talla',
|
||||||
|
'brand_label' => 'Marca/Productor',
|
||||||
|
'additional_info_label' => 'Información adicional',
|
||||||
|
'additional_info_placeholder' => 'p.ej. pequeña mancha en la manga, falta un botón',
|
||||||
|
'generate_button' => 'Generar descripción',
|
||||||
|
'generated_result' => 'Resultado generado',
|
||||||
|
'output_title' => 'Título',
|
||||||
|
'output_short_description' => 'Descripción corta',
|
||||||
|
'output_description' => 'Descripción',
|
||||||
|
'output_measurements' => 'Medidas',
|
||||||
|
'output_hashtags' => 'Hashtags',
|
||||||
|
'copy_to_clipboard' => 'Copiar',
|
||||||
|
'approve' => 'Aprobar',
|
||||||
|
'error_parsing_ai_response' => 'Error: No se pudo interpretar la respuesta de la IA. Por favor, inténtalo de nuevo.',
|
||||||
|
'error_calling_ai_api' => 'Error: No se pudo conectar con el servicio de IA. Por favor, inténtalo más tarde.',
|
||||||
|
'error_required_fields' => 'Error: Por favor, rellena todos los campos obligatorios (Estado y Talla).',
|
||||||
|
'register_title' => 'Registrarse',
|
||||||
|
'create_account' => 'Crea tu cuenta',
|
||||||
|
'register_subtitle' => 'Únete a Captionista para empezar a generar descripciones.',
|
||||||
|
'email' => 'Correo electrónico',
|
||||||
|
'password' => 'Contraseña',
|
||||||
|
'confirm_password' => 'Confirmar contraseña',
|
||||||
|
'register_button' => 'Registrarse',
|
||||||
|
'already_have_account' => '¿Ya tienes una cuenta?',
|
||||||
|
'login_link' => 'Iniciar sesión',
|
||||||
|
'login_title' => 'Iniciar sesión',
|
||||||
|
'login_heading' => '¡Bienvenido de nuevo!',
|
||||||
|
'login_subtitle' => 'Inicia sesión para acceder a tu cuenta.',
|
||||||
|
'login_button' => 'Iniciar sesión',
|
||||||
|
'no_account' => '¿No tienes una cuenta?',
|
||||||
|
'register_link' => 'Registrarse',
|
||||||
|
'fill_all_fields' => 'Por favor, rellena todos los campos.',
|
||||||
|
'passwords_do_not_match' => 'Las contraseñas no coinciden.',
|
||||||
|
'invalid_email' => 'Formato de correo electrónico no válido.',
|
||||||
|
'email_already_registered' => 'Este correo electrónico ya está registrado.',
|
||||||
|
'registration_successful' => '¡Registro exitoso! Ahora puedes iniciar sesión.',
|
||||||
|
'login_now' => 'Iniciar sesión ahora.',
|
||||||
|
'invalid_credentials' => 'Correo electrónico o contraseña no válidos.',
|
||||||
|
'profile_link' => 'Perfil',
|
||||||
|
'profile_heading' => 'Tu Perfil',
|
||||||
|
'profile_subtitle' => 'Actualiza los datos de tu cuenta.',
|
||||||
|
'profile_updated_successfully' => 'Perfil actualizado correctamente.',
|
||||||
|
'email_cannot_be_changed' => 'El correo electrónico no se puede cambiar.',
|
||||||
|
'new_password' => 'Nueva contraseña',
|
||||||
|
'confirm_new_password' => 'Confirmar nueva contraseña',
|
||||||
|
'update_profile_button' => 'Actualizar perfil',
|
||||||
|
'history_title' => 'Historial de Generación',
|
||||||
|
'history_empty' => 'Aún no has generado ninguna descripción.',
|
||||||
|
'history_generate_now' => 'Genera tu primera descripción',
|
||||||
|
'generation_date' => 'Generado el',
|
||||||
|
'nav_pricing' => 'Precios',
|
||||||
|
'nav_faq' => 'FAQ',
|
||||||
|
'nav_terms' => 'Términos de Uso',
|
||||||
|
'pricing_subtitle' => 'Elige el plan que más te convenga.',
|
||||||
|
'monthly' => 'Mensual',
|
||||||
|
'annually' => 'Anual',
|
||||||
|
'save_15_percent' => 'Ahorra 15%',
|
||||||
|
'basic_plan_name' => 'Básico',
|
||||||
|
'premium_plan_name' => 'Premium',
|
||||||
|
'basic_plan_limit' => '150 generaciones/mes',
|
||||||
|
'premium_plan_limit' => 'Generaciones ilimitadas',
|
||||||
|
'feature_150_generations' => '150 generaciones por mes',
|
||||||
|
'feature_unlimited_generations' => 'Generaciones ilimitadas',
|
||||||
|
'feature_standard_support' => 'Soporte estándar por correo electrónico',
|
||||||
|
'feature_priority_support' => 'Soporte prioritario por correo electrónico',
|
||||||
|
'feature_history_access' => 'Acceso al historial de generaciones',
|
||||||
|
'choose_plan' => 'Elegir Plan',
|
||||||
|
'month_short' => 'mes',
|
||||||
|
'billed_annually_at' => 'Facturado anualmente a',
|
||||||
|
'pricing_footer_text' => 'Puedes mejorar, degradar o cancelar tu suscripción en cualquier momento. No hay reembolsos por períodos parciales.',
|
||||||
|
'faq_title' => 'Preguntas Frecuentes',
|
||||||
|
'faq_q1_title' => '¿Cómo funciona la suscripción?',
|
||||||
|
'faq_q1_text' => 'Puedes elegir entre una suscripción mensual o anual. El plan anual te ofrece un 15% de descuento. Tu suscripción se renovará automáticamente al final de cada ciclo de facturación.',
|
||||||
|
'faq_q2_title' => '¿Puedo cancelar mi suscripción?',
|
||||||
|
'faq_q2_text' => 'Sí, puedes cancelar tu suscripción en cualquier momento. Si cancelas, tu suscripción permanecerá activa hasta el final del período de facturación actual y no se renovará. No ofrecemos reembolsos por períodos de suscripción parciales.',
|
||||||
|
'faq_q3_title' => '¿Puedo cambiar mi plan?',
|
||||||
|
'faq_q3_text' => 'Sí, puedes mejorar o degradar tu plan en cualquier momento. Los cambios entrarán en vigor en el próximo ciclo de facturación.',
|
||||||
|
'faq_q4_title' => '¿Qué pasa si excedo mi límite de generaciones en el plan Básico?',
|
||||||
|
'faq_q4_text' => 'Si alcanzas el límite de 150 generaciones en el plan Básico, deberás actualizar al plan Premium para continuar generando descripciones en el mismo mes.',
|
||||||
|
'terms_title' => 'Términos de Uso',
|
||||||
|
'terms_last_updated' => 'Última actualización: 9 de noviembre de 2025',
|
||||||
|
'terms_section1_title' => '1. Aceptación de los Términos',
|
||||||
|
'terms_section1_text' => 'Al acceder y utilizar nuestro servicio, aceptas y te comprometes a estar sujeto a los términos y disposiciones de este acuerdo. Además, al utilizar estos servicios particulares, estarás sujeto a cualquier guía o regla publicada aplicable a dichos servicios.',
|
||||||
|
'terms_section2_title' => '2. Cuentas de Usuario',
|
||||||
|
'terms_section2_text' => 'Eres responsable de mantener la confidencialidad de tu cuenta y contraseña. Aceptas la responsabilidad de todas las actividades que ocurran bajo tu cuenta o contraseña.',
|
||||||
|
'terms_section3_title' => '3. Suscripción y Pago',
|
||||||
|
'terms_section3_text' => 'Todas las suscripciones se facturan por adelantado de forma mensual o anual y no son reembolsables. Tu suscripción se renovará automáticamente a menos que la canceles.',
|
||||||
|
'terms_section4_title' => '4. Terminación',
|
||||||
|
'terms_section4_text' => 'Podemos rescindir o suspender tu cuenta y prohibir el acceso al servicio de inmediato, sin previo aviso ni responsabilidad, a nuestra entera discreción, por cualquier motivo y sin limitación, incluido, entre otros, el incumplimiento de los Términos.',
|
||||||
|
'terms_section5_title' => '5. Cambios en los Términos',
|
||||||
|
'terms_section5_text' => 'Nos reservamos el derecho, a nuestra entera discreción, de modificar o reemplazar estos Términos en cualquier momento. Proporcionaremos un aviso de al menos 30 días antes de que los nuevos términos entren en vigor.',
|
||||||
|
],
|
||||||
|
'fr' => [
|
||||||
|
'app_name' => 'Captionista',
|
||||||
|
'nav_generator' => 'Générateur',
|
||||||
|
'nav_history' => 'Historique',
|
||||||
|
'nav_subscription' => 'Abonnement',
|
||||||
|
'nav_login' => 'Connexion',
|
||||||
|
'nav_register' => "S'inscrire",
|
||||||
|
'nav_logout' => 'Déconnexion',
|
||||||
|
'nav_profile' => 'Profil',
|
||||||
|
'hero_title' => 'Descriptions pour votre boutique de vêtements optimisées par l´IA.',
|
||||||
|
'hero_subtitle' => 'Créez sans effort des fiches produits attrayantes et optimisées pour le SEO en quelques secondes.',
|
||||||
|
'hero_cta' => 'Commencez gratuitement',
|
||||||
|
'how_it_works_title' => 'Comment ça marche',
|
||||||
|
'step1_title' => 'Télécharger',
|
||||||
|
'step1_desc' => 'Téléchargez jusqu´à 5 photos de votre produit.',
|
||||||
|
'step2_title' => 'Saisir les détails',
|
||||||
|
'step2_desc' => 'Ajoutez des informations clés comme le matériau, la taille et la marque.',
|
||||||
|
'step3_title' => 'Générer',
|
||||||
|
'step3_desc' => 'Recevez une description complète et prête à l´emploi.',
|
||||||
|
'features_title' => 'Fonctionnalités',
|
||||||
|
'feature1_title' => 'Descriptions par l´IA',
|
||||||
|
'feature2_title' => 'Support multilingue',
|
||||||
|
'feature3_title' => 'Historique et exportation',
|
||||||
|
'pricing_title' => 'Tarifs',
|
||||||
|
'pricing_free' => 'Gratuit',
|
||||||
|
'pricing_paid' => 'Payant',
|
||||||
|
'pricing_premium' => 'Premium',
|
||||||
|
'pricing_coming_soon' => 'Bientôt disponible',
|
||||||
|
'footer_copyright' => '© 2025 Captionista. Tous droits réservés.',
|
||||||
|
'generator_form_title' => 'Créez votre description',
|
||||||
|
'upload_label' => 'Télécharger des photos (jusqu´à 5)',
|
||||||
|
'state_label' => 'État',
|
||||||
|
'state_new' => 'Neuf',
|
||||||
|
'state_used' => 'D´occasion',
|
||||||
|
'state_visibly_used' => 'Visiblement utilisé',
|
||||||
|
'length_label' => 'Longueur (cm)',
|
||||||
|
'width_chest_label' => 'Largeur de poitrine (cm)',
|
||||||
|
'width_waist_label' => 'Largeur de taille (cm)',
|
||||||
|
'width_hips_label' => 'Largeur de hanches (cm)',
|
||||||
|
'material_label' => 'Matériau',
|
||||||
|
'size_label' => 'Taille',
|
||||||
|
'brand_label' => 'Marque/Producteur',
|
||||||
|
'additional_info_label' => 'Informations supplémentaires',
|
||||||
|
'additional_info_placeholder' => 'ex. petite tache sur la manche, bouton manquant',
|
||||||
|
'generate_button' => 'Générer la description',
|
||||||
|
'generated_result' => 'Résultat généré',
|
||||||
|
'output_title' => 'Titre',
|
||||||
|
'output_short_description' => 'Description courte',
|
||||||
|
'output_description' => 'Description',
|
||||||
|
'output_measurements' => 'Mesures',
|
||||||
|
'output_hashtags' => 'Hashtags',
|
||||||
|
'copy_to_clipboard' => 'Copier',
|
||||||
|
'approve' => 'Approuver',
|
||||||
|
'error_parsing_ai_response' => 'Erreur : Impossible d´interpréter la réponse de l´IA. Veuillez réessayer.',
|
||||||
|
'error_calling_ai_api' => 'Erreur : Impossible de se connecter au service d´IA. Veuillez réessayer plus tard.',
|
||||||
|
'error_required_fields' => 'Erreur : Veuillez remplir tous les champs obligatoires (État et Taille).',
|
||||||
|
'register_title' => "S'inscrire",
|
||||||
|
'create_account' => 'Créez votre compte',
|
||||||
|
'register_subtitle' => 'Rejoignez Captionista pour commencer à générer des descriptions.',
|
||||||
|
'email' => 'Email',
|
||||||
|
'password' => 'Mot de passe',
|
||||||
|
'confirm_password' => 'Confirmez le mot de passe',
|
||||||
|
'register_button' => "S'inscrire",
|
||||||
|
'already_have_account' => 'Vous avez déjà un compte ?',
|
||||||
|
'login_link' => 'Connexion',
|
||||||
|
'login_title' => 'Connexion',
|
||||||
|
'login_heading' => 'Content de vous revoir !',
|
||||||
|
'login_subtitle' => 'Connectez-vous pour accéder à votre compte.',
|
||||||
|
'login_button' => 'Connexion',
|
||||||
|
'no_account' => "Vous n'avez pas de compte ?",
|
||||||
|
'register_link' => "S'inscrire",
|
||||||
|
'fill_all_fields' => 'Veuillez remplir tous les champs.',
|
||||||
|
'passwords_do_not_match' => 'Les mots de passe ne correspondent pas.',
|
||||||
|
'invalid_email' => "Format d'email invalide.",
|
||||||
|
'email_already_registered' => 'Cet email est déjà enregistré.',
|
||||||
|
'registration_successful' => 'Inscription réussie ! Vous pouvez maintenant vous connecter.',
|
||||||
|
'login_now' => 'Connectez-vous maintenant.',
|
||||||
|
'invalid_credentials' => 'Email ou mot de passe invalide.',
|
||||||
|
'profile_link' => 'Profil',
|
||||||
|
'profile_heading' => 'Votre profil',
|
||||||
|
'profile_subtitle' => 'Mettez à jour les détails de votre compte.',
|
||||||
|
'profile_updated_successfully' => 'Profil mis à jour avec succès.',
|
||||||
|
'email_cannot_be_changed' => "L'email ne peut pas être modifié.",
|
||||||
|
'new_password' => 'Nouveau mot de passe',
|
||||||
|
'confirm_new_password' => 'Confirmer le nouveau mot de passe',
|
||||||
|
'update_profile_button' => 'Mettre à jour le profil',
|
||||||
|
'history_title' => 'Historique des générations',
|
||||||
|
'history_empty' => "Vous n'a encore généré aucune description.",
|
||||||
|
'history_generate_now' => 'Générez votre première description',
|
||||||
|
'generation_date' => 'Généré le',
|
||||||
|
'nav_pricing' => 'Tarifs',
|
||||||
|
'nav_faq' => 'FAQ',
|
||||||
|
'nav_terms' => "Conditions d'utilisation",
|
||||||
|
'pricing_subtitle' => 'Choisissez le plan qui vous convient.',
|
||||||
|
'monthly' => 'Mensuel',
|
||||||
|
'annually' => 'Annuel',
|
||||||
|
'save_15_percent' => 'Économisez 15%',
|
||||||
|
'basic_plan_name' => 'Basique',
|
||||||
|
'premium_plan_name' => 'Premium',
|
||||||
|
'basic_plan_limit' => '150 générations/mois',
|
||||||
|
'premium_plan_limit' => 'Générations illimitées',
|
||||||
|
'feature_150_generations' => '150 générations par mois',
|
||||||
|
'feature_unlimited_generations' => 'Générations illimitées',
|
||||||
|
'feature_standard_support' => 'Support par email standard',
|
||||||
|
'feature_priority_support' => 'Support par email prioritaire',
|
||||||
|
'feature_history_access' => "Accès à l'historique des générations",
|
||||||
|
'choose_plan' => 'Choisir le plan',
|
||||||
|
'month_short' => 'mois',
|
||||||
|
'billed_annually_at' => 'Facturé annuellement à',
|
||||||
|
'pricing_footer_text' => 'Vous pouvez mettre à niveau, rétrograder ou annuler votre abonnement à tout moment. Aucun remboursement pour les périodes partielles.',
|
||||||
|
'faq_title' => 'Foire Aux Questions',
|
||||||
|
'faq_q1_title' => "Comment fonctionne l'abonnement ?",
|
||||||
|
'faq_q1_text' => 'Vous pouvez choisir entre un abonnement mensuel ou annuel. Le plan annuel vous offre une réduction de 15%. Votre abonnement se renouvellera automatiquement à la fin de chaque cycle de facturation.',
|
||||||
|
'faq_q2_title' => 'Puis-je annuler mon abonnement ?',
|
||||||
|
'faq_q2_text' => "Oui, vous pouvez annuler votre abonnement à tout moment. Si vous annulez, votre abonnement restera actif jusqu'à la fin de la période de facturation en cours et ne sera pas renouvelé. Nous n'offrons pas de remboursement pour les périodes d'abonnement partielles.",
|
||||||
|
'faq_q3_title' => 'Puis-je changer de plan ?',
|
||||||
|
'faq_q3_text' => 'Oui, vous pouvez mettre à niveau ou rétrograder votre plan à tout moment. Les modifications prendront effet lors du prochain cycle de facturation.',
|
||||||
|
'faq_q4_title' => 'Que se passe-t-il si je dépasse ma limite de générations avec le plan Basique ?',
|
||||||
|
'faq_q4_text' => 'Si vous atteignez la limite de 150 générations avec le plan Basique, vous devrez passer au plan Premium pour continuer à générer des descriptions au cours du même mois.',
|
||||||
|
'terms_title' => "Conditions d'utilisation",
|
||||||
|
'terms_last_updated' => 'Dernière mise à jour : 9 novembre 2025',
|
||||||
|
'terms_section1_title' => '1. Acceptation des conditions',
|
||||||
|
'terms_section1_text' => "En accédant à notre service et en l'utilisant, vous acceptez d'être lié par les termes et dispositions de cet accord. De plus, en utilisant ces services particuliers, vous serez soumis à toutes les directives ou règles publiées applicables à ces services.",
|
||||||
|
'terms_section2_title' => "2. Comptes d'utilisateurs",
|
||||||
|
'terms_section2_text' => 'Vous êtes responsable du maintien de la confidentialité de votre compte et de votre mot de passe. Vous acceptez d'assumer la responsabilité de toutes les activités qui se déroulent sous votre compte ou votre mot de passe.',
|
||||||
|
'terms_section3_title' => '3. Abonnement et paiement',
|
||||||
|
'terms_section3_text' => "Tous les abonnements sont facturés à l'avance sur une base mensuelle ou annuelle et ne sont pas remboursables. Votre abonnement sera automatiquement renouvelé, sauf si vous l'annulez.",
|
||||||
|
'terms_section4_title' => '4. Résiliation',
|
||||||
|
'terms_section4_text' => "Nous pouvons résilier ou suspendre votre compte et interdire l'accès au service immédiatement, sans préavis ni responsabilité, à notre seule discrétion, pour quelque raison que ce soit et sans limitation, y compris, mais sans s'y limiter, une violation des Conditions.",
|
||||||
|
'terms_section5_title' => '5. Modifications des conditions',
|
||||||
|
'terms_section5_text' => "Nous nous réservons le droit, à notre seule discrétion, de modifier ou de remplacer ces Conditions à tout moment. Nous fournirons un préavis d'au moins 30 jours avant l'entrée en vigueur de nouvelles conditions.",
|
||||||
|
],
|
||||||
|
'de' => [
|
||||||
|
'app_name' => 'Captionista',
|
||||||
|
'nav_generator' => 'Generator',
|
||||||
|
'nav_history' => 'Verlauf',
|
||||||
|
'nav_subscription' => 'Abonnement',
|
||||||
|
'nav_login' => 'Anmelden',
|
||||||
|
'nav_register' => 'Registrieren',
|
||||||
|
'nav_logout' => 'Abmelden',
|
||||||
|
'nav_profile' => 'Profil',
|
||||||
|
'hero_title' => 'KI-gestützte Beschreibungen für Ihren Bekleidungsladen.',
|
||||||
|
'hero_subtitle' => 'Erstellen Sie mühelos überzeugende, SEO-freundliche Produktbeschreibungen in Sekunden.',
|
||||||
|
'hero_cta' => 'Kostenlos starten',
|
||||||
|
'how_it_works_title' => 'Wie es funktioniert',
|
||||||
|
'step1_title' => 'Hochladen',
|
||||||
|
'step1_desc' => 'Laden Sie bis zu 5 Fotos Ihres Produkts hoch.',
|
||||||
|
'step2_title' => 'Details eingeben',
|
||||||
|
'step2_desc' => 'Fügen Sie wichtige Informationen wie Material, Größe und Marke hinzu.',
|
||||||
|
'step3_title' => 'Generieren',
|
||||||
|
'step3_desc' => 'Erhalten Sie eine vollständige, gebrauchsfertige Beschreibung.',
|
||||||
|
'features_title' => 'Funktionen',
|
||||||
|
'feature1_title' => 'KI-gestützte Beschreibungen',
|
||||||
|
'feature2_title' => 'Mehrsprachige Unterstützung',
|
||||||
|
'feature3_title' => 'Verlauf & Export',
|
||||||
|
'pricing_title' => 'Preise',
|
||||||
|
'pricing_free' => 'Kostenlos',
|
||||||
|
'pricing_paid' => 'Bezahlt',
|
||||||
|
'pricing_premium' => 'Premium',
|
||||||
|
'pricing_coming_soon' => 'Demnächst verfügbar',
|
||||||
|
'footer_copyright' => '© 2025 Captionista. Alle Rechte vorbehalten.',
|
||||||
|
'generator_form_title' => 'Erstellen Sie Ihre Beschreibung',
|
||||||
|
'upload_label' => 'Fotos hochladen (bis zu 5)',
|
||||||
|
'state_label' => 'Zustand',
|
||||||
|
'state_new' => 'Neu',
|
||||||
|
'state_used' => 'Gebraucht',
|
||||||
|
'state_visibly_used' => 'Sichtlich gebraucht',
|
||||||
|
'length_label' => 'Länge (cm)',
|
||||||
|
'width_chest_label' => 'Brustweite (cm)',
|
||||||
|
'width_waist_label' => 'Taillenweite (cm)',
|
||||||
|
'width_hips_label' => 'Hüftweite (cm)',
|
||||||
|
'material_label' => 'Material',
|
||||||
|
'size_label' => 'Größe',
|
||||||
|
'brand_label' => 'Marke/Hersteller',
|
||||||
|
'additional_info_label' => 'Zusätzliche Informationen',
|
||||||
|
'additional_info_placeholder' => 'z.B. kleiner Fleck am Ärmel, fehlender Knopf',
|
||||||
|
'generate_button' => 'Beschreibung generieren',
|
||||||
|
'generated_result' => 'Generiertes Ergebnis',
|
||||||
|
'output_title' => 'Titel',
|
||||||
|
'output_short_description' => 'Kurzbeschreibung',
|
||||||
|
'output_description' => 'Beschreibung',
|
||||||
|
'output_measurements' => 'Maße',
|
||||||
|
'output_hashtags' => 'Hashtags',
|
||||||
|
'copy_to_clipboard' => 'Kopieren',
|
||||||
|
'approve' => 'Genehmigen',
|
||||||
|
'error_parsing_ai_response' => 'Fehler: Die KI-Antwort konnte nicht interpretiert werden. Bitte versuchen Sie es erneut.',
|
||||||
|
'error_calling_ai_api' => 'Fehler: Der KI-Dienst konnte nicht erreicht werden. Bitte versuchen Sie es später erneut.',
|
||||||
|
'error_required_fields' => 'Fehler: Bitte füllen Sie alle Pflichtfelder aus (Zustand und Größe).',
|
||||||
|
'register_title' => 'Registrieren',
|
||||||
|
'create_account' => 'Erstellen Sie Ihr Konto',
|
||||||
|
'register_subtitle' => 'Treten Sie Captionista bei, um mit dem Generieren von Beschreibungen zu beginnen.',
|
||||||
|
'email' => 'E-Mail',
|
||||||
|
'password' => 'Passwort',
|
||||||
|
'confirm_password' => 'Passwort bestätigen',
|
||||||
|
'register_button' => 'Registrieren',
|
||||||
|
'already_have_account' => 'Haben Sie bereits ein Konto?',
|
||||||
|
'login_link' => 'Anmelden',
|
||||||
|
'login_title' => 'Anmelden',
|
||||||
|
'login_heading' => 'Willkommen zurück!',
|
||||||
|
'login_subtitle' => 'Melden Sie sich an, um auf Ihr Konto zuzugreifen.',
|
||||||
|
'login_button' => 'Anmelden',
|
||||||
|
'no_account' => 'Haben Sie kein Konto?',
|
||||||
|
'register_link' => 'Registrieren',
|
||||||
|
'fill_all_fields' => 'Bitte füllen Sie alle Felder aus.',
|
||||||
|
'passwords_do_not_match' => 'Die Passwörter stimmen nicht überein.',
|
||||||
|
'invalid_email' => 'Ungültiges E-Mail-Format.',
|
||||||
|
'email_already_registered' => 'Diese E-Mail ist bereits registriert.',
|
||||||
|
'registration_successful' => 'Registrierung erfolgreich! Sie können sich jetzt anmelden.',
|
||||||
|
'login_now' => 'Jetzt anmelden.',
|
||||||
|
'invalid_credentials' => 'Ungültige E-Mail oder ungültiges Passwort.',
|
||||||
|
'profile_link' => 'Profil',
|
||||||
|
'profile_heading' => 'Ihr Profil',
|
||||||
|
'profile_subtitle' => 'Aktualisieren Sie Ihre Kontodaten.',
|
||||||
|
'profile_updated_successfully' => 'Profil erfolgreich aktualisiert.',
|
||||||
|
'email_cannot_be_changed' => 'E-Mail kann nicht geändert werden.',
|
||||||
|
'new_password' => 'Neues Passwort',
|
||||||
|
'confirm_new_password' => 'Neues Passwort bestätigen',
|
||||||
|
'update_profile_button' => 'Profil aktualisieren',
|
||||||
|
'history_title' => 'Generierungsverlauf',
|
||||||
|
'history_empty' => 'Sie haben noch keine Beschreibungen generiert.',
|
||||||
|
'history_generate_now' => 'Erstellen Sie Ihre erste Beschreibung',
|
||||||
|
'generation_date' => 'Erstellt am',
|
||||||
|
'nav_pricing' => 'Preise',
|
||||||
|
'nav_faq' => 'FAQ',
|
||||||
|
'nav_terms' => 'Nutzungsbedingungen',
|
||||||
|
'pricing_subtitle' => 'Wählen Sie den Plan, der für Sie am besten geeignet ist.',
|
||||||
|
'monthly' => 'Monatlich',
|
||||||
|
'annually' => 'Jährlich',
|
||||||
|
'save_15_percent' => '15% sparen',
|
||||||
|
'basic_plan_name' => 'Basis',
|
||||||
|
'premium_plan_name' => 'Premium',
|
||||||
|
'basic_plan_limit' => '150 Generierungen/Monat',
|
||||||
|
'premium_plan_limit' => 'Unbegrenzte Generierungen',
|
||||||
|
'feature_150_generations' => '150 Generierungen pro Monat',
|
||||||
|
'feature_unlimited_generations' => 'Unbegrenzte Generierungen',
|
||||||
|
'feature_standard_support' => 'Standard-E-Mail-Support',
|
||||||
|
'feature_priority_support' => 'Prioritäts-E-Mail-Support',
|
||||||
|
'feature_history_access' => 'Zugriff auf den Generierungsverlauf',
|
||||||
|
'choose_plan' => 'Plan wählen',
|
||||||
|
'month_short' => 'Monat',
|
||||||
|
'billed_annually_at' => 'Wird jährlich abgerechnet mit',
|
||||||
|
'pricing_footer_text' => 'Sie können Ihr Abonnement jederzeit upgraden, downgraden oder kündigen. Keine Rückerstattung für Teilzeiträume.',
|
||||||
|
'faq_title' => 'Häufig gestellte Fragen',
|
||||||
|
'faq_q1_title' => 'Wie funktioniert das Abonnement?',
|
||||||
|
'faq_q1_text' => 'Sie können zwischen einem monatlichen oder jährlichen Abonnement wählen. Mit dem Jahresplan erhalten Sie 15% Rabatt. Ihr Abonnement verlängert sich automatisch am Ende jedes Abrechnungszeitraums.',
|
||||||
|
'faq_q2_title' => 'Kann ich mein Abonnement kündigen?',
|
||||||
|
'faq_q2_text' => 'Ja, Sie können Ihr Abonnement jederzeit kündigen. Wenn Sie kündigen, bleibt Ihr Abonnement bis zum Ende des aktuellen Abrechnungszeitraums aktiv und wird nicht verlängert. Wir bieten keine Rückerstattungen für Teil-Abonnementzeiträume an.',
|
||||||
|
'faq_q3_title' => 'Kann ich meinen Plan ändern?',
|
||||||
|
'faq_q3_text' => 'Ja, Sie können Ihren Plan jederzeit upgraden oder downgraden. Die Änderungen werden im nächsten Abrechnungszyklus wirksam.',
|
||||||
|
'faq_q4_title' => 'Was passiert, wenn ich mein Generierungslimit im Basis-Plan überschreite?',
|
||||||
|
'faq_q4_text' => 'Wenn Sie das Limit von 150 Generierungen im Basis-Plan erreichen, müssen Sie auf den Premium-Plan upgraden, um im selben Monat weiterhin Beschreibungen zu generieren.',
|
||||||
|
'terms_title' => 'Nutzungsbedingungen',
|
||||||
|
'terms_last_updated' => 'Zuletzt aktualisiert: 9. November 2025',
|
||||||
|
'terms_section1_title' => '1. Annahme der Bedingungen',
|
||||||
|
'terms_section1_text' => 'Durch den Zugriff auf und die Nutzung unseres Dienstes akzeptieren Sie die Bedingungen dieser Vereinbarung und stimmen diesen zu. Darüber hinaus unterliegen Sie bei der Nutzung dieser speziellen Dienste allen veröffentlichten Richtlinien oder Regeln, die für solche Dienste gelten.',
|
||||||
|
'terms_section2_title' => '2. Benutzerkonten',
|
||||||
|
'terms_section2_text' => 'Sie sind für die Wahrung der Vertraulichkeit Ihres Kontos und Passworts verantwortlich. Sie erklären sich damit einverstanden, die Verantwortung für alle Aktivitäten zu übernehmen, die unter Ihrem Konto oder Passwort stattfinden.',
|
||||||
|
'terms_section3_title' => '3. Abonnement und Zahlung',
|
||||||
|
'terms_section3_text' => 'Alle Abonnements werden im Voraus auf monatlicher oder jährlicher Basis abgerechnet und sind nicht erstattungsfähig. Ihr Abonnement verlängert sich automatisch, es sei denn, Sie kündigen es.',
|
||||||
|
'terms_section4_title' => '4. Kündigung',
|
||||||
|
'terms_section4_text' => 'Wir können Ihr Konto kündigen oder sperren und den Zugang zum Dienst sofort, ohne vorherige Ankündigung oder Haftung, nach unserem alleinigen Ermessen, aus irgendeinem Grund und ohne Einschränkung, einschließlich, aber nicht beschränkt auf einen Verstoß gegen die Bedingungen, sperren.',
|
||||||
|
'terms_section5_title' => '5. Änderungen der Bedingungen',
|
||||||
|
'terms_section5_text' => 'Wir behalten uns das Recht vor, diese Bedingungen nach unserem alleinigen Ermessen jederzeit zu ändern oder zu ersetzen. Wir werden mindestens 30 Tage vor dem Inkrafttreten neuer Bedingungen eine entsprechende Mitteilung machen.',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function t($key, $lang = null) {
|
||||||
|
global $current_lang;
|
||||||
|
if ($lang === null) {
|
||||||
|
$lang = $current_lang;
|
||||||
|
}
|
||||||
|
$translations = get_translations();
|
||||||
|
return $translations[$lang][$key] ?? $key;
|
||||||
|
}
|
||||||
258
index.php
258
index.php
@ -1,150 +1,118 @@
|
|||||||
<?php
|
<?php require_once 'includes/header.php'; ?>
|
||||||
declare(strict_types=1);
|
|
||||||
@ini_set('display_errors', '1');
|
|
||||||
@error_reporting(E_ALL);
|
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>New Style</title>
|
|
||||||
<?php
|
|
||||||
// Read project preview data from environment
|
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|
||||||
?>
|
|
||||||
<?php if ($projectDescription): ?>
|
|
||||||
<!-- Meta description -->
|
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
|
||||||
<!-- Open Graph meta tags -->
|
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<!-- Twitter meta tags -->
|
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($projectImageUrl): ?>
|
|
||||||
<!-- Open Graph image -->
|
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<!-- Twitter image -->
|
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<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);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
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: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
<main>
|
||||||
<div class="card">
|
<section class="hero">
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<div class="container">
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<h1><?php echo t('hero_title'); ?></h1>
|
||||||
<span class="sr-only">Loading…</span>
|
<p><?php echo t('hero_subtitle'); ?></p>
|
||||||
|
<a href="#generator" class="cta-button"><?php echo t('hero_cta'); ?></a>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
</section>
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
<section id="generator" class="generator-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2><?php echo t('generator_form_title'); ?></h2>
|
||||||
|
<form action="generate.php" method="POST" class="generator-form" enctype="multipart/form-data">
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-column-wide">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="photo-upload"><?php echo t('upload_label'); ?></label>
|
||||||
|
<div class="photo-upload-container">
|
||||||
|
<input type="file" id="photo-upload" name="photos[]" multiple accept="image/*" style="display: none;">
|
||||||
|
<div class="upload-area" id="upload-area">
|
||||||
|
<i data-feather="upload-cloud"></i>
|
||||||
|
<p>Click to browse or drag & drop your images here</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="photo-preview" id="photo-preview"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-column">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="state"><?php echo t('state_label'); ?></label>
|
||||||
|
<select id="state" name="state" required>
|
||||||
|
<option value="new"><?php echo t('state_new'); ?></option>
|
||||||
|
<option value="used"><?php echo t('state_used'); ?></option>
|
||||||
|
<option value="visibly_used"><?php echo t('state_visibly_used'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="material"><?php echo t('material_label'); ?></label>
|
||||||
|
<input type="text" id="material" name="material" placeholder="e.g., Cotton, Polyester">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="brand"><?php echo t('brand_label'); ?></label>
|
||||||
|
<input type="text" id="brand" name="brand" placeholder="e.g., Zara, Nike">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="size"><?php echo t('size_label'); ?></label>
|
||||||
|
<input type="text" id="size" name="size" placeholder="e.g., M, 38, 10" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-column">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="length"><?php echo t('length_label'); ?></label>
|
||||||
|
<input type="number" id="length" name="length" placeholder="e.g., 88">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="chest-width"><?php echo t('width_chest_label'); ?></label>
|
||||||
|
<input type="number" id="chest-width" name="chest_width" placeholder="e.g., 45">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="waist-width"><?php echo t('width_waist_label'); ?></label>
|
||||||
|
<input type="number" id="waist-width" name="waist_width" placeholder="e.g., 35">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hips-width"><?php echo t('width_hips_label'); ?></label>
|
||||||
|
<input type="number" id="hips-width" name="hips_width" placeholder="e.g., 50">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="additional-info"><?php echo t('additional_info_label'); ?></label>
|
||||||
|
<textarea id="additional-info" name="additional_info" rows="3" placeholder="<?php echo t('additional_info_placeholder'); ?>"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-submit-group">
|
||||||
|
<button type="submit" class="cta-button"><?php echo t('generate_button'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if (isset($_SESSION['generation_result'])) {
|
||||||
|
echo $_SESSION['generation_result'];
|
||||||
|
unset($_SESSION['generation_result']);
|
||||||
|
}
|
||||||
|
if (isset($_SESSION['generation_error'])) {
|
||||||
|
echo '<div class="error-message">' . $_SESSION['generation_error'] . '</div>';
|
||||||
|
unset($_SESSION['generation_error']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="how-it-works">
|
||||||
|
<div class="container">
|
||||||
|
<h2><?php echo t('how_it_works_title'); ?></h2>
|
||||||
|
<div class="steps">
|
||||||
|
<div class="step">
|
||||||
|
<div class="step-icon"><i data-feather="upload-cloud"></i></div>
|
||||||
|
<h3><?php echo t('step1_title'); ?></h3>
|
||||||
|
<p><?php echo t('step1_desc'); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="step">
|
||||||
|
<div class="step-icon"><i data-feather="edit-3"></i></div>
|
||||||
|
<h3><?php echo t('step2_title'); ?></h3>
|
||||||
|
<p><?php echo t('step2_desc'); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="step">
|
||||||
|
<div class="step-icon"><i data-feather="zap"></i></div>
|
||||||
|
<h3><?php echo t('step3_title'); ?></h3>
|
||||||
|
<p><?php echo t('step3_desc'); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<?php include 'includes/footer.php'; ?>
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
61
login.php
Normal file
61
login.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$email = $_POST['email'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($email) || empty($password)) {
|
||||||
|
$error = t('fill_all_fields');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare('SELECT id, password FROM users WHERE email = ?');
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['user_email'] = $email;
|
||||||
|
header('Location: index.php');
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$error = t('invalid_credentials');
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error = "Database error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<main class="container">
|
||||||
|
<section class="auth-form">
|
||||||
|
<h1><?= t('login_heading') ?></h1>
|
||||||
|
<p><?= t('login_subtitle') ?></p>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="error-message"><?= $error ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="login.php" method="POST" id="login-form" novalidate>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email"><?= t('email') ?></label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password"><?= t('password') ?></label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary"><?= t('login_button') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="auth-switch"><?= t('no_account') ?> <a href="register.php"><?= t('register_link') ?></a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
6
logout.php
Normal file
6
logout.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
header('Location: index.php');
|
||||||
|
exit();
|
||||||
84
pricing.php
Normal file
84
pricing.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'includes/translations.php';
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
|
||||||
|
// Default to monthly
|
||||||
|
$billing_cycle = isset($_GET['billing_cycle']) && $_GET['billing_cycle'] === 'annually' ? 'annually' : 'monthly';
|
||||||
|
|
||||||
|
$plans = [
|
||||||
|
'basic' => [
|
||||||
|
'name' => t('basic_plan_name'),
|
||||||
|
'price_monthly' => 10,
|
||||||
|
'price_annually' => 10 * 12 * 0.85,
|
||||||
|
'limit' => t('basic_plan_limit'),
|
||||||
|
'features' => [
|
||||||
|
t('feature_150_generations'),
|
||||||
|
t('feature_standard_support'),
|
||||||
|
t('feature_history_access')
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'premium' => [
|
||||||
|
'name' => t('premium_plan_name'),
|
||||||
|
'price_monthly' => 20,
|
||||||
|
'price_annually' => 20 * 12 * 0.85,
|
||||||
|
'limit' => t('premium_plan_limit'),
|
||||||
|
'features' => [
|
||||||
|
t('feature_unlimited_generations'),
|
||||||
|
t('feature_priority_support'),
|
||||||
|
t('feature_history_access')
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="container my-5">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h1><?php echo t('pricing_title'); ?></h1>
|
||||||
|
<p class="lead"><?php echo t('pricing_subtitle'); ?></p>
|
||||||
|
<div class="btn-group" role="group" aria-label="Billing cycle toggle">
|
||||||
|
<a href="?billing_cycle=monthly" class="btn <?php echo $billing_cycle === 'monthly' ? 'btn-primary' : 'btn-outline-primary'; ?>"><?php echo t('monthly'); ?></a>
|
||||||
|
<a href="?billing_cycle=annually" class="btn <?php echo $billing_cycle === 'annually' ? 'btn-primary' : 'btn-outline-primary'; ?>">
|
||||||
|
<?php echo t('annually'); ?>
|
||||||
|
<span class="badge bg-success ms-2"><?php echo t('save_15_percent'); ?></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach ($plans as $plan_key => $plan): ?>
|
||||||
|
<div class="col-lg-6 mb-4">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h4 class="my-0 fw-normal"><?php echo $plan['name']; ?></h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title pricing-card-title text-center">
|
||||||
|
$<?php echo $billing_cycle === 'monthly' ? number_format($plan['price_monthly'], 2) : number_format($plan['price_annually'] / 12, 2); ?>
|
||||||
|
<small class="text-muted fw-light">/<?php echo t('month_short'); ?></small>
|
||||||
|
</h1>
|
||||||
|
<?php if ($billing_cycle === 'annually'): ?>
|
||||||
|
<p class="text-center text-muted"><?php echo t('billed_annually_at'); ?> $<?php echo number_format($plan['price_annually'], 2); ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<ul class="list-unstyled mt-3 mb-4">
|
||||||
|
<?php foreach ($plan['features'] as $feature): ?>
|
||||||
|
<li class="mb-2"><i class="fas fa-check text-success me-2"></i><?php echo $feature; ?></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="subscription.php?plan=<?php echo $plan_key; ?>&billing_cycle=<?php echo $billing_cycle; ?>" class="btn btn-lg btn-primary"><?php echo t('choose_plan'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 text-center">
|
||||||
|
<p><?php echo t('pricing_footer_text'); ?></p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php require_once 'includes/footer.php'; ?>
|
||||||
69
profile.php
Normal file
69
profile.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Redirect to login if not authenticated
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
$success = null;
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
$password_confirm = $_POST['password_confirm'] ?? '';
|
||||||
|
|
||||||
|
if (!empty($password) && $password === $password_confirm) {
|
||||||
|
try {
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET password = ? WHERE id = ?');
|
||||||
|
$stmt->execute([$hashed_password, $user_id]);
|
||||||
|
$success = t('profile_updated_successfully');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error = "Database error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
} elseif (!empty($password)) {
|
||||||
|
$error = t('passwords_do_not_match');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<section class="auth-form">
|
||||||
|
<h1><?= t('profile_heading') ?></h1>
|
||||||
|
<p><?= t('profile_subtitle') ?></p>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="error-message"><?= $error ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="success-message"><?= $success ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="profile.php" method="POST" id="profile-form" novalidate>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email"><?= t('email') ?></label>
|
||||||
|
<input type="email" id="email" name="email" value="<?= htmlspecialchars($_SESSION['user_email'] ?? '') ?>" disabled>
|
||||||
|
<small><?= t('email_cannot_be_changed') ?></small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password"><?= t('new_password') ?></label>
|
||||||
|
<input type="password" id="password" name="password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirm"><?= t('confirm_new_password') ?></label>
|
||||||
|
<input type="password" id="password_confirm" name="password_confirm">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary"><?= t('update_profile_button') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php require_once 'includes/footer.php'; ?>
|
||||||
75
register.php
Normal file
75
register.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
$success = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$email = $_POST['email'] ?? '';
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
$password_confirm = $_POST['password_confirm'] ?? '';
|
||||||
|
|
||||||
|
if (empty($email) || empty($password) || empty($password_confirm)) {
|
||||||
|
$error = t('fill_all_fields');
|
||||||
|
} elseif ($password !== $password_confirm) {
|
||||||
|
$error = t('passwords_do_not_match');
|
||||||
|
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$error = t('invalid_email');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
$error = t('email_already_registered');
|
||||||
|
} else {
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO users (email, password) VALUES (?, ?)');
|
||||||
|
$stmt->execute([$email, $hashed_password]);
|
||||||
|
$success = t('registration_successful');
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error = "Database error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<main class="container">
|
||||||
|
<section class="auth-form">
|
||||||
|
<h1><?= t('create_account') ?></h1>
|
||||||
|
<p><?= t('register_subtitle') ?></p>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="error-message"><?= $error ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="success-message">
|
||||||
|
<?= $success ?>
|
||||||
|
<a href="login.php"><?= t('login_now') ?></a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="register.php" method="POST" id="register-form" novalidate>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email"><?= t('email') ?></label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password"><?= t('password') ?></label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirm"><?= t('confirm_password') ?></label>
|
||||||
|
<input type="password" id="password_confirm" name="password_confirm" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary"><?= t('register_button') ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="auth-switch"><?= t('already_have_account') ?> <a href="login.php"><?= t('login_link') ?></a></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
38
subscription.php
Normal file
38
subscription.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
// The header.php file now handles session_start(), login checks, translations, and language selection.
|
||||||
|
// We include it at the top of the <body>.
|
||||||
|
|
||||||
|
if (session_status() == PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<?php echo $_SESSION['lang'] ?? 'en'; ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?= t('nav_subscription') ?> - Captionista</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.29.0/feather.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
</head>
|
||||||
|
<body class="light-mode">
|
||||||
|
|
||||||
|
<?php include 'includes/header.php'; ?>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<h1><?= t('nav_subscription') ?></h1>
|
||||||
|
<p><?= t('pricing_coming_soon') ?></p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php include 'includes/footer.php'; ?>
|
||||||
|
|
||||||
|
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
terms.php
Normal file
29
terms.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'includes/translations.php';
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main class="container my-5">
|
||||||
|
<h1 class="text-center mb-4"><?php echo t('terms_title'); ?></h1>
|
||||||
|
<p class="text-muted text-center mb-5"><?php echo t('terms_last_updated'); ?></p>
|
||||||
|
|
||||||
|
<div class="card card-body">
|
||||||
|
<h4>1. <?php echo t('terms_section1_title'); ?></h4>
|
||||||
|
<p><?php echo t('terms_section1_text'); ?></p>
|
||||||
|
|
||||||
|
<h4>2. <?php echo t('terms_section2_title'); ?></h4>
|
||||||
|
<p><?php echo t('terms_section2_text'); ?></p>
|
||||||
|
|
||||||
|
<h4>3. <?php echo t('terms_section3_title'); ?></h4>
|
||||||
|
<p><?php echo t('terms_section3_text'); ?></p>
|
||||||
|
|
||||||
|
<h4>4. <?php echo t('terms_section4_title'); ?></h4>
|
||||||
|
<p><?php echo t('terms_section4_text'); ?></p>
|
||||||
|
|
||||||
|
<h4>5. <?php echo t('terms_section5_title'); ?></h4>
|
||||||
|
<p><?php echo t('terms_section5_text'); ?></p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php require_once 'includes/footer.php'; ?>
|
||||||
Loading…
x
Reference in New Issue
Block a user