generated from bisco/codex-bootstrap
feat: polish booking and confirmation UI
This commit is contained in:
@@ -31,23 +31,37 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<p class="eyebrow">Booking</p>
|
<p class="eyebrow">Booking</p>
|
||||||
<h1>Reserve seats</h1>
|
<h1>Reserve seats</h1>
|
||||||
<p class="supporting">Performance {{ performanceId }}. Complete the form and we will send a confirmation email.</p>
|
<p class="supporting">
|
||||||
|
Performance {{ performanceId }}. Complete the form and we will email a confirmation link before any reservation becomes active.
|
||||||
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<mat-card class="content-card">
|
<mat-card class="content-card">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
@if (isSuccess()) {
|
@if (isSuccess()) {
|
||||||
<div class="status-copy success" aria-live="polite">
|
<div class="status-panel success" aria-live="polite">
|
||||||
<mat-icon>check_circle</mat-icon>
|
<div class="status-icon">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">mark_email_read</mat-icon>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Reservation created</h2>
|
<h2>Reservation created</h2>
|
||||||
<p>check your email</p>
|
<p>Check your email to confirm the booking and unlock the QR code for admission.</p>
|
||||||
|
<div class="status-steps">
|
||||||
|
<span><mat-icon fontSet="material-symbols-outlined">mail</mat-icon> Open the confirmation email</span>
|
||||||
|
<span><mat-icon fontSet="material-symbols-outlined">verified</mat-icon> Confirm your reservation</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<form [formGroup]="bookingForm" (ngSubmit)="submit()" novalidate>
|
<form [formGroup]="bookingForm" (ngSubmit)="submit()" novalidate>
|
||||||
|
<div class="intro-note">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">info</mat-icon>
|
||||||
|
<p>We only ask for the essentials. Your seats are held only after email confirmation.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-icon matPrefix fontSet="material-symbols-outlined">person</mat-icon>
|
||||||
<mat-label>Name</mat-label>
|
<mat-label>Name</mat-label>
|
||||||
<input matInput type="text" formControlName="name" autocomplete="name" />
|
<input matInput type="text" formControlName="name" autocomplete="name" />
|
||||||
@if (bookingForm.controls.name.touched && bookingForm.controls.name.hasError('required')) {
|
@if (bookingForm.controls.name.touched && bookingForm.controls.name.hasError('required')) {
|
||||||
@@ -56,8 +70,10 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-icon matPrefix fontSet="material-symbols-outlined">mail</mat-icon>
|
||||||
<mat-label>Email</mat-label>
|
<mat-label>Email</mat-label>
|
||||||
<input matInput type="email" formControlName="email" autocomplete="email" />
|
<input matInput type="email" formControlName="email" autocomplete="email" />
|
||||||
|
<mat-hint>We will send the confirmation link here.</mat-hint>
|
||||||
@if (bookingForm.controls.email.touched && bookingForm.controls.email.hasError('required')) {
|
@if (bookingForm.controls.email.touched && bookingForm.controls.email.hasError('required')) {
|
||||||
<mat-error>Email is required.</mat-error>
|
<mat-error>Email is required.</mat-error>
|
||||||
}
|
}
|
||||||
@@ -67,8 +83,10 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-icon matPrefix fontSet="material-symbols-outlined">group</mat-icon>
|
||||||
<mat-label>Number of seats</mat-label>
|
<mat-label>Number of seats</mat-label>
|
||||||
<input matInput type="number" min="1" step="1" formControlName="partySize" />
|
<input matInput type="number" min="1" step="1" formControlName="partySize" />
|
||||||
|
<mat-hint>Enter the total number of guests in your party.</mat-hint>
|
||||||
@if (bookingForm.controls.partySize.touched && bookingForm.controls.partySize.hasError('required')) {
|
@if (bookingForm.controls.partySize.touched && bookingForm.controls.partySize.hasError('required')) {
|
||||||
<mat-error>Number of seats is required.</mat-error>
|
<mat-error>Number of seats is required.</mat-error>
|
||||||
}
|
}
|
||||||
@@ -79,14 +97,21 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (submitError()) {
|
@if (submitError()) {
|
||||||
<p class="error-message" aria-live="assertive">{{ submitError() }}</p>
|
<div class="message-panel error" aria-live="assertive">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">error</mat-icon>
|
||||||
|
<p>{{ submitError() }}</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (fieldErrors().length > 0) {
|
@if (fieldErrors().length > 0) {
|
||||||
<div class="field-errors" aria-live="assertive">
|
<div class="message-panel error field-errors" aria-live="assertive">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">warning</mat-icon>
|
||||||
|
<div>
|
||||||
|
<p class="message-title">Please check the highlighted details:</p>
|
||||||
@for (message of fieldErrors(); track message) {
|
@for (message of fieldErrors(); track message) {
|
||||||
<p>{{ message }}</p>
|
<p>{{ message }}</p>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +121,10 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
<mat-progress-spinner mode="indeterminate" diameter="18"></mat-progress-spinner>
|
<mat-progress-spinner mode="indeterminate" diameter="18"></mat-progress-spinner>
|
||||||
<span>Submitting...</span>
|
<span>Submitting...</span>
|
||||||
} @else {
|
} @else {
|
||||||
<span>Reserve</span>
|
<ng-container>
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">confirmation_number</mat-icon>
|
||||||
|
<span>Reserve</span>
|
||||||
|
</ng-container>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
<a mat-button routerLink="/shows">Back to shows</a>
|
<a mat-button routerLink="/shows">Back to shows</a>
|
||||||
@@ -138,10 +166,35 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content-card {
|
.content-card {
|
||||||
border-radius: 8px;
|
border-radius: 20px;
|
||||||
border: 1px solid var(--azionelab-border);
|
border: 1px solid var(--azionelab-border);
|
||||||
background: var(--azionelab-surface);
|
background: var(--azionelab-surface-strong);
|
||||||
box-shadow: var(--azionelab-shadow);
|
box-shadow: var(--azionelab-shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-content {
|
||||||
|
padding: 28px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-note {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(159, 47, 40, 0.06);
|
||||||
|
color: var(--azionelab-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-note p {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-note mat-icon {
|
||||||
|
color: var(--azionelab-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-grid {
|
.form-grid {
|
||||||
@@ -153,18 +206,37 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message,
|
.message-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-panel.error {
|
||||||
|
background: var(--azionelab-error-bg);
|
||||||
|
border-color: var(--azionelab-error-border);
|
||||||
|
color: var(--azionelab-error-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-panel p,
|
||||||
.field-errors p {
|
.field-errors p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #b3261e;
|
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-size: 0.92rem;
|
font-size: 0.92rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-errors {
|
.message-title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-errors > div {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
margin-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
@@ -183,23 +255,77 @@ type ApiValidationErrors = Record<string, string[]>;
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy {
|
.status-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 14px;
|
gap: 16px;
|
||||||
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy h2 {
|
.status-panel h2 {
|
||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy p {
|
.status-panel p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--azionelab-muted);
|
color: var(--azionelab-muted);
|
||||||
|
line-height: 1.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy.success mat-icon {
|
.status-panel.success {
|
||||||
color: #2e7d32;
|
padding: 22px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: var(--azionelab-success-bg);
|
||||||
|
border: 1px solid var(--azionelab-success-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(46, 125, 50, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon mat-icon {
|
||||||
|
color: var(--azionelab-success-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-steps {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-steps span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
color: var(--azionelab-success-ink);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-steps mat-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
mat-card-content {
|
||||||
|
padding: 22px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel,
|
||||||
|
.message-panel,
|
||||||
|
.intro-note {
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`],
|
`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error'
|
|||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<p class="eyebrow">Reservation confirmation</p>
|
<p class="eyebrow">Reservation confirmation</p>
|
||||||
<h1>Email confirmation</h1>
|
<h1>Email confirmation</h1>
|
||||||
|
<p class="supporting">Use the link from your inbox to confirm your reservation and retrieve the QR code for venue check-in.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<mat-card class="status-card">
|
<mat-card class="status-card">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
@if (state() === 'loading') {
|
@if (state() === 'loading') {
|
||||||
<div class="status-copy" aria-live="polite">
|
<div class="status-panel loading" aria-live="polite">
|
||||||
<mat-progress-spinner mode="indeterminate" diameter="36"></mat-progress-spinner>
|
<mat-progress-spinner mode="indeterminate" diameter="36"></mat-progress-spinner>
|
||||||
<div>
|
<div>
|
||||||
<h2>Confirming reservation...</h2>
|
<h2>Confirming reservation...</h2>
|
||||||
@@ -40,28 +41,40 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error'
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if (state() === 'success' && confirmation()) {
|
@if (state() === 'success' && confirmation()) {
|
||||||
<div class="status-copy success" aria-live="polite">
|
<div class="status-panel success" aria-live="polite">
|
||||||
<mat-icon>check_circle</mat-icon>
|
<div class="status-icon">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">verified</mat-icon>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Reservation confirmed</h2>
|
<h2>Reservation confirmed</h2>
|
||||||
<p>Your seats are confirmed. Present this QR code at check-in.</p>
|
<p>Your seats are confirmed. Present this QR code at check-in and keep the link handy if staff needs manual access.</p>
|
||||||
|
<div class="success-points">
|
||||||
|
<span><mat-icon fontSet="material-symbols-outlined">qr_code_2</mat-icon> Ready for entry</span>
|
||||||
|
<span><mat-icon fontSet="material-symbols-outlined">theater_comedy</mat-icon> See you at the performance</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (confirmation()!.qr_code_image) {
|
@if (confirmation()!.qr_code_image) {
|
||||||
<div class="qr-panel">
|
<div class="qr-panel">
|
||||||
|
<p class="panel-label">Your check-in QR code</p>
|
||||||
<img [src]="confirmation()!.qr_code_image" alt="Reservation QR code" />
|
<img [src]="confirmation()!.qr_code_image" alt="Reservation QR code" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (confirmation()!.qr_code_url) {
|
@if (confirmation()!.qr_code_url) {
|
||||||
<p class="meta">Check-in URL: <a [href]="confirmation()!.qr_code_url">{{ confirmation()!.qr_code_url }}</a></p>
|
<div class="meta-card">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">link</mat-icon>
|
||||||
|
<p>Check-in URL: <a [href]="confirmation()!.qr_code_url">{{ confirmation()!.qr_code_url }}</a></p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (state() === 'invalid') {
|
@if (state() === 'invalid') {
|
||||||
<div class="status-copy" aria-live="assertive">
|
<div class="status-panel error" aria-live="assertive">
|
||||||
<mat-icon>error</mat-icon>
|
<div class="status-icon">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">error</mat-icon>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Invalid confirmation link</h2>
|
<h2>Invalid confirmation link</h2>
|
||||||
<p>This token is not valid. Please use the latest email confirmation link.</p>
|
<p>This token is not valid. Please use the latest email confirmation link.</p>
|
||||||
@@ -70,8 +83,10 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error'
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if (state() === 'expired') {
|
@if (state() === 'expired') {
|
||||||
<div class="status-copy" aria-live="assertive">
|
<div class="status-panel warning" aria-live="assertive">
|
||||||
<mat-icon>schedule</mat-icon>
|
<div class="status-icon">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">schedule</mat-icon>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Confirmation link expired</h2>
|
<h2>Confirmation link expired</h2>
|
||||||
<p>This link has expired. Please create a new reservation.</p>
|
<p>This link has expired. Please create a new reservation.</p>
|
||||||
@@ -80,8 +95,10 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error'
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if (state() === 'error') {
|
@if (state() === 'error') {
|
||||||
<div class="status-copy" aria-live="assertive">
|
<div class="status-panel error" aria-live="assertive">
|
||||||
<mat-icon>warning</mat-icon>
|
<div class="status-icon">
|
||||||
|
<mat-icon fontSet="material-symbols-outlined">warning</mat-icon>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Could not confirm reservation</h2>
|
<h2>Could not confirm reservation</h2>
|
||||||
<p>Please try again in a moment.</p>
|
<p>Please try again in a moment.</p>
|
||||||
@@ -120,54 +137,195 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error'
|
|||||||
font-size: clamp(2rem, 4vw, 3rem);
|
font-size: clamp(2rem, 4vw, 3rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card {
|
.supporting {
|
||||||
border-radius: 8px;
|
color: var(--azionelab-muted);
|
||||||
border: 1px solid var(--azionelab-border);
|
line-height: 1.6;
|
||||||
background: var(--azionelab-surface);
|
max-width: 58ch;
|
||||||
box-shadow: var(--azionelab-shadow);
|
margin: 14px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy {
|
.status-card {
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid var(--azionelab-border);
|
||||||
|
background: var(--azionelab-surface-strong);
|
||||||
|
box-shadow: var(--azionelab-shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-content {
|
||||||
|
padding: 28px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-actions {
|
||||||
|
padding: 0 28px 24px !important;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 14px;
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy h2 {
|
.status-panel h2 {
|
||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy p {
|
.status-panel p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--azionelab-muted);
|
color: var(--azionelab-muted);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-copy.success mat-icon {
|
.status-panel.loading {
|
||||||
color: #2e7d32;
|
background: rgba(159, 47, 40, 0.04);
|
||||||
|
border-color: rgba(159, 47, 40, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.success {
|
||||||
|
background: var(--azionelab-success-bg);
|
||||||
|
border-color: var(--azionelab-success-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.warning {
|
||||||
|
background: #fff7ea;
|
||||||
|
border-color: rgba(181, 126, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.error {
|
||||||
|
background: var(--azionelab-error-bg);
|
||||||
|
border-color: var(--azionelab-error-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 16px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background: rgba(30, 27, 24, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.success .status-icon {
|
||||||
|
background: rgba(46, 125, 50, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.warning .status-icon {
|
||||||
|
background: rgba(181, 126, 0, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.error .status-icon {
|
||||||
|
background: rgba(179, 38, 30, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.success .status-icon mat-icon {
|
||||||
|
color: var(--azionelab-success-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.warning .status-icon mat-icon {
|
||||||
|
color: #9b6c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel.error .status-icon mat-icon {
|
||||||
|
color: var(--azionelab-error-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-points {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-points span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
color: var(--azionelab-success-ink);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-points mat-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-panel {
|
.qr-panel {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
padding: 14px;
|
padding: 16px;
|
||||||
border-radius: 8px;
|
border-radius: 18px;
|
||||||
border: 1px solid var(--azionelab-border);
|
border: 1px solid var(--azionelab-border);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-label {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--azionelab-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
.qr-panel img {
|
.qr-panel img {
|
||||||
width: min(280px, 100%);
|
width: min(280px, 100%);
|
||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta {
|
.meta-card {
|
||||||
margin: 14px 0 0;
|
display: flex;
|
||||||
word-break: break-word;
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(159, 47, 40, 0.05);
|
||||||
color: var(--azionelab-muted);
|
color: var(--azionelab-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meta-card p {
|
||||||
|
margin: 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card a {
|
||||||
|
color: var(--azionelab-accent-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
mat-card-content {
|
||||||
|
padding: 22px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-actions {
|
||||||
|
padding: 0 22px 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel {
|
||||||
|
padding: 18px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-panel {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-panel img {
|
||||||
|
width: min(100%, 280px);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
`],
|
`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
<title>AzioneLab</title>
|
<title>AzioneLab</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<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=Fraunces:opsz,wght@9..144,600;9..144,700&family=Manrope:wght@400;500;600;700&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,500,0,0"
|
||||||
|
rel="stylesheet"
|
||||||
|
>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
:root {
|
:root {
|
||||||
--azionelab-bg: #f3eee6;
|
--azionelab-bg: #f3eee6;
|
||||||
--azionelab-surface: rgba(255, 255, 255, 0.78);
|
--azionelab-surface: rgba(255, 255, 255, 0.78);
|
||||||
|
--azionelab-surface-strong: rgba(255, 255, 255, 0.92);
|
||||||
--azionelab-ink: #1e1b18;
|
--azionelab-ink: #1e1b18;
|
||||||
--azionelab-muted: #645b53;
|
--azionelab-muted: #645b53;
|
||||||
--azionelab-accent: #9f2f28;
|
--azionelab-accent: #9f2f28;
|
||||||
--azionelab-accent-strong: #7f211c;
|
--azionelab-accent-strong: #7f211c;
|
||||||
--azionelab-border: rgba(30, 27, 24, 0.12);
|
--azionelab-border: rgba(30, 27, 24, 0.12);
|
||||||
--azionelab-shadow: 0 18px 48px rgba(46, 28, 18, 0.12);
|
--azionelab-shadow: 0 18px 48px rgba(46, 28, 18, 0.12);
|
||||||
|
--azionelab-success-bg: #edf7ef;
|
||||||
|
--azionelab-success-ink: #1f5f2b;
|
||||||
|
--azionelab-success-border: rgba(46, 125, 50, 0.18);
|
||||||
|
--azionelab-error-bg: #fff3f1;
|
||||||
|
--azionelab-error-ink: #8b2a20;
|
||||||
|
--azionelab-error-border: rgba(179, 38, 30, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -16,7 +23,7 @@
|
|||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
font-family: "Segoe UI", "Helvetica Neue", sans-serif;
|
font-family: "Manrope", "Segoe UI", sans-serif;
|
||||||
color: var(--azionelab-ink);
|
color: var(--azionelab-ink);
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top right, rgba(159, 47, 40, 0.12), transparent 28%),
|
radial-gradient(circle at top right, rgba(159, 47, 40, 0.12), transparent 28%),
|
||||||
@@ -28,6 +35,19 @@ body {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
font-family: "Fraunces", "Times New Roman", serif;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
button, input, textarea {
|
button, input, textarea {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings:
|
||||||
|
'FILL' 0,
|
||||||
|
'wght' 500,
|
||||||
|
'GRAD' 0,
|
||||||
|
'opsz' 24;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user