mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Improve font pair and color strategy questionnaire screens
Font pair screen: drop the phone footer from the type boards, widen the hero picture, split the editorial band evenly with the card row, center the desktop cards, and fix the label line heights. The handset takes a 26px headline, stepped section spacing instead of one uniform gap, and a single centered proof item between chevrons. Every word slot on these boards now draws lorem rather than product copy, so a pair is judged on its setting. Color strategy screen: one-line question, an options panel that hugs its rows, a preview whose height is free again, and the CTA stack in the same place the font screen keeps it. Also nudges the type scale specimen's top and left padding. Prepared with AI assistance (Cursor). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -480,13 +480,11 @@ const hook = type ? '' : undefined;
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div class="ps-phone-footer">
|
||||
{type ? (
|
||||
<Fragment><i data-type-footer-link></i><i data-type-footer-link></i><i data-type-footer-link></i></Fragment>
|
||||
) : (
|
||||
{!type && (
|
||||
<div class="ps-phone-footer">
|
||||
<Fragment><i></i><i></i><i></i></Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
+13
-19
@@ -734,7 +734,7 @@ const questions = [
|
||||
<section class="picker-screen" data-screen="03" data-step="Color strategy" aria-hidden="true" aria-labelledby="picker-strategy-title">
|
||||
<div class="picker-container">
|
||||
<div class="picker-strategy">
|
||||
<h2 id="picker-strategy-title" class="picker-question-title">How much color<br />should this design use?</h2>
|
||||
<h2 id="picker-strategy-title" class="picker-question-title">How much color should this design use?</h2>
|
||||
|
||||
<div class="picker-strategy-grid">
|
||||
<div class="picker-type-rail">
|
||||
@@ -808,27 +808,21 @@ const questions = [
|
||||
<p class="picker-band-status" data-reorder-status role="status" aria-live="polite"></p>
|
||||
</div>
|
||||
|
||||
<div class="picker-strategy-subgrid">
|
||||
{/* The stage the chosen surfaces are drawn on. Its contents are
|
||||
lifted from the tiles on screen 01b, so it is filled by
|
||||
script. The clones land inside the stage, which is why the
|
||||
buttons are its sibling rather than its last child. */}
|
||||
<div class="picker-strategy-stage" data-surface-stage>
|
||||
<div class="picker-strategy-stage" data-surface-stage>
|
||||
<div class="picker-surface-tabs" {...surfaceTabsAttrs('color-strategy')} hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="picker-actions-stack">
|
||||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||||
Select this strategy
|
||||
<span class="ks-button-arrow" aria-hidden="true">
|
||||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="picker-actions-stack">
|
||||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||||
Select this strategy
|
||||
<span class="ks-button-arrow" aria-hidden="true">
|
||||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,52 @@ const LOREM = {
|
||||
caption: 'Lorem ipsum dolor sit amet, consectetur adipiscing.',
|
||||
};
|
||||
|
||||
/* Every word slot on the font boards, in lorem. A pair is judged on glyphs, and
|
||||
product words pull the eye into reading the page instead of the setting, so
|
||||
the type screen draws placeholder copy in every slot rather than only in the
|
||||
running text. Titles are one word because the slot is one line wide. */
|
||||
const LOREM_PREVIEW = {
|
||||
brand: 'Lo',
|
||||
nav: ['Lorem', 'Ipsum', 'Dolor', 'Amet'],
|
||||
navAction: 'Consectetur',
|
||||
menuAction: 'Ipsum',
|
||||
ctaPrimary: 'Lorem ipsum',
|
||||
ctaSecondary: 'Dolor sit',
|
||||
proof: ['Lorem ipsum dolor', 'Sit amet elit', 'Sed do eiusmod', 'Tempor incididunt'],
|
||||
sectionTitle: 'Lorem ipsum dolor sit',
|
||||
sectionLink: 'Consectetur adipiscing',
|
||||
gallery: [
|
||||
{ title: 'Lorem', meta: 'Ipsum dolor' },
|
||||
{ title: 'Dolor', meta: 'Sit amet' },
|
||||
{ title: 'Eiusmod', meta: 'Tempor elit' },
|
||||
],
|
||||
footerLinks: ['Lorem', 'Ipsum', 'Dolor', 'Amet'],
|
||||
footerMark: '© Lorem ipsum',
|
||||
};
|
||||
|
||||
const LOREM_SPECIMEN = { headline: 'Lorem ipsum dolor sit amet' };
|
||||
|
||||
const LOREM_APP = {
|
||||
rail: ['Lorem', 'Ipsum', 'Dolor'],
|
||||
columns: ['Lorem', 'Ipsum', 'Dolor'],
|
||||
figures: ['1,284', '98.2%', '41'],
|
||||
amounts: ['$12,400', '$3,860', '$9,215'],
|
||||
panel: ['Lorem ipsum', 'Dolor sit amet'],
|
||||
switches: ['Lorem ipsum', 'Dolor sit'],
|
||||
chartTitle: 'Lorem ipsum dolor',
|
||||
lanes: ['Lorem', 'Ipsum', 'Dolor', 'Amet', 'Elit'],
|
||||
};
|
||||
|
||||
const LOREM_DOCS = {
|
||||
rail: ['Lorem ipsum', 'Dolor', 'Consectetur', 'Adipiscing elit'],
|
||||
crumb: 'Lorem / Ipsum dolor',
|
||||
note: 'Nota',
|
||||
};
|
||||
|
||||
const LOREM_INDEX = {
|
||||
stops: ['Lorem', 'Ipsum', 'Dolor', 'Amet'],
|
||||
};
|
||||
|
||||
/* The desktop artboard sets three cards and the phone two, so a fourth would
|
||||
be words the agent writes and nobody ever reads. */
|
||||
const GALLERY_CARDS = 3;
|
||||
@@ -358,13 +404,14 @@ function fillIndexed(root, selector, values) {
|
||||
and a board takes the slots its surface has and leaves the rest alone. The
|
||||
desktop and the phone are filled separately because the indexed slots start
|
||||
counting again on each. */
|
||||
function fillBoard(board, preview, specimen) {
|
||||
function fillBoard(board) {
|
||||
const preview = LOREM_PREVIEW;
|
||||
const specimen = LOREM_SPECIMEN;
|
||||
const desktop = board.querySelector('.ps-desktop');
|
||||
const phoneBody = board.querySelector('.ps-phone-body');
|
||||
const phoneFooter = board.querySelector('.ps-phone-footer');
|
||||
// A rail means the sections of a document on one board and the areas of a
|
||||
// tool on the other, so it is the one slot whose words the surface decides.
|
||||
const rail = board.dataset.surface === 'read' ? DOCS.rail : APP.rail;
|
||||
const rail = board.dataset.surface === 'read' ? LOREM_DOCS.rail : LOREM_APP.rail;
|
||||
const fill = (selector, value) => {
|
||||
for (const node of board.querySelectorAll(selector)) node.textContent = value;
|
||||
};
|
||||
@@ -379,11 +426,11 @@ function fillBoard(board, preview, specimen) {
|
||||
fill('[data-type-section-body]', LOREM.paragraph);
|
||||
fill('[data-type-section-link]', preview.sectionLink);
|
||||
fill('[data-type-footer-mark]', preview.footerMark);
|
||||
fill('[data-type-note-label]', DOCS.note);
|
||||
fill('[data-type-note-label]', LOREM_DOCS.note);
|
||||
fill('[data-type-note-body]', LOREM.note);
|
||||
fill('[data-type-crumb]', DOCS.crumb);
|
||||
fill('[data-type-crumb]', LOREM_DOCS.crumb);
|
||||
fill('[data-type-caption]', LOREM.caption);
|
||||
fill('[data-type-chart-title]', APP.chartTitle);
|
||||
fill('[data-type-chart-title]', LOREM_APP.chartTitle);
|
||||
for (const card of [desktop, phoneBody]) {
|
||||
fillIndexed(card, '[data-type-nav]', preview.nav);
|
||||
fillIndexed(card, '[data-type-proof]', preview.proof);
|
||||
@@ -391,27 +438,19 @@ function fillBoard(board, preview, specimen) {
|
||||
fillIndexed(card, '[data-type-gallery-meta]', preview.gallery.map(({ meta }) => meta));
|
||||
fillIndexed(card, '[data-type-passage]', LOREM.passages);
|
||||
fillIndexed(card, '[data-type-item]', LOREM.items);
|
||||
fillIndexed(card, '[data-type-stop]', INDEX.stops);
|
||||
fillIndexed(card, '[data-type-stop]', LOREM_INDEX.stops);
|
||||
fillIndexed(card, '[data-type-rail]', rail);
|
||||
fillIndexed(card, '[data-type-lane]', APP.lanes);
|
||||
fillIndexed(card, '[data-type-column]', APP.columns);
|
||||
fillIndexed(card, '[data-type-figure]', APP.figures);
|
||||
fillIndexed(card, '[data-type-amount]', APP.amounts);
|
||||
fillIndexed(card, '[data-type-panel]', APP.panel);
|
||||
fillIndexed(card, '[data-type-switch]', APP.switches);
|
||||
fillIndexed(card, '[data-type-lane]', LOREM_APP.lanes);
|
||||
fillIndexed(card, '[data-type-column]', LOREM_APP.columns);
|
||||
fillIndexed(card, '[data-type-figure]', LOREM_APP.figures);
|
||||
fillIndexed(card, '[data-type-amount]', LOREM_APP.amounts);
|
||||
fillIndexed(card, '[data-type-panel]', LOREM_APP.panel);
|
||||
fillIndexed(card, '[data-type-switch]', LOREM_APP.switches);
|
||||
}
|
||||
fillIndexed(desktop?.querySelector('.ps-footer'), '[data-type-footer-link]', preview.footerLinks);
|
||||
fillIndexed(phoneFooter, '[data-type-footer-link]', preview.footerLinks);
|
||||
}
|
||||
|
||||
function syncFontPair(pair) {
|
||||
const manifest = fontManifest;
|
||||
const preview = {
|
||||
...FALLBACK_FONTS.preview,
|
||||
...manifest.preview,
|
||||
...pair.preview,
|
||||
};
|
||||
const specimen = { ...manifest.specimen, ...pair.specimen };
|
||||
// Written once on the stage the boards share, and on screen 05's two preview
|
||||
// columns, which are set in the pair chosen here so the faces travel with it.
|
||||
for (const target of [typeStage, scaleSheet, scaleSpecimen]) {
|
||||
@@ -419,7 +458,7 @@ function syncFontPair(pair) {
|
||||
target.style.setProperty('--pt-body', fontStack(pair.body.family));
|
||||
target.style.setProperty('--pt-heading-weight', pair.heading.weight);
|
||||
}
|
||||
for (const board of typeBoards) fillBoard(board, preview, specimen);
|
||||
for (const board of typeBoards) fillBoard(board);
|
||||
document.querySelector('[name="font-heading"]').value = pair.heading.family;
|
||||
document.querySelector('[name="font-body"]').value = pair.body.family;
|
||||
document.querySelector('[name="font-heading-source"]').value = pair.heading.source || '';
|
||||
|
||||
+97
-66
@@ -2723,54 +2723,15 @@ body.picker-page {
|
||||
grid with the buttons underneath it and leaves this one 90px short of what it
|
||||
now owns. */
|
||||
.picker-screen[data-screen="03"] .picker-strategy-grid {
|
||||
--pk-fit: calc(100svh - var(--pk-chrome, 250px));
|
||||
max-height: var(--pk-fit);
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
row-gap: 14px;
|
||||
max-height: calc(100svh - var(--pk-chrome, 346px));
|
||||
}
|
||||
|
||||
.picker-screen[data-screen="03"] .picker-strategy-grid > .picker-type-rail {
|
||||
grid-row: 1 / -1;
|
||||
grid-template-rows: subgrid;
|
||||
/* The rail is measured against the viewport rather than against the frame
|
||||
beside it. The frame draws the chosen surface at that drawing's own ratio,
|
||||
which is wider than the artboard this column used to hold, so matching its
|
||||
height would spend the whole column on four rows of 78px.
|
||||
|
||||
The band no longer has to give on a short viewport: the row it sits in is
|
||||
shared with the CTA stack, which is 90px whatever the viewport does, so
|
||||
anything the band gave back would be spent on empty space beside the
|
||||
buttons. */
|
||||
--pk-band: 96px;
|
||||
height: auto;
|
||||
align-self: start;
|
||||
grid-template-rows: auto auto auto;
|
||||
}
|
||||
|
||||
/* These four rows are what the top row of the screen measures, and the frame
|
||||
beside them takes whatever they come to, so this is also where the drawing's
|
||||
height is decided.
|
||||
|
||||
Floored at 88px, which is the tallest option's two lines plus the air they
|
||||
need to read as a paragraph rather than a block. Below that the row has no
|
||||
padding left to give and the sentence is what gets cut, so past the floor the
|
||||
panel is allowed to run past the budget and scroll instead. */
|
||||
.picker-screen[data-screen="03"] .picker-strategy-choices.picker-type-options {
|
||||
/* The row formula below is the only cap this panel needs. The shared one
|
||||
measures against the column budget alone, which on a short viewport asks
|
||||
for less than the floor and clips the fourth option to reach it. */
|
||||
max-height: none;
|
||||
/* The row is the panel's own height at every size the budget clears, so
|
||||
filling it costs nothing there and buys the scroll on the sizes where the
|
||||
four rows have stopped fitting. */
|
||||
align-self: stretch;
|
||||
/* The 16 the rows do not get is the row the palette and the buttons share,
|
||||
less the grid's own row gap and the panel's hairline. */
|
||||
grid-auto-rows: max(88px, min(108px, calc((var(--pk-fit) - var(--pk-band) - 16px) / 4)));
|
||||
}
|
||||
|
||||
/* Equal rows, sized by the tallest one's copy rather than by how many there
|
||||
are. Stretching the tracks to the artboard's height instead makes a row on a
|
||||
three-option screen half again as tall as the same row on a four-option one,
|
||||
and the difference is spent on empty space under two lines of text. The
|
||||
panel hugs the rows it has, so nothing draws an empty band underneath. */
|
||||
.picker-strategy-choices.picker-type-options {
|
||||
align-self: start;
|
||||
max-height: var(--pk-column);
|
||||
@@ -2960,25 +2921,9 @@ body.picker-page {
|
||||
hold either way. Persuade is still drawn in pixels and sits loose in a frame
|
||||
this size.
|
||||
============================================================ */
|
||||
.picker-strategy-subgrid {
|
||||
min-width: 0;
|
||||
grid-row: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-rows: subgrid;
|
||||
}
|
||||
|
||||
/* Centred on the frame rather than on the page, which is where the question was
|
||||
answered. The row is sized for the palette across from it and the buttons come
|
||||
to a few pixels less, so they take the middle of it. */
|
||||
.picker-strategy-subgrid > .picker-actions-stack {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
/* The stage is the frame's own box plus the strip over it: a row for the tab
|
||||
strip and a row for the frame, so the strip's height is room the layout
|
||||
reserved rather than room taken off the question above. */
|
||||
.picker-strategy-stage {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
}
|
||||
@@ -2992,6 +2937,8 @@ body.picker-page {
|
||||
/* The frame runs down to the bottom of the choices beside it, so it takes the
|
||||
row's height rather than the 16 / 7 it carries everywhere else. */
|
||||
aspect-ratio: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* The tiles and the palette screen show the drawing at a fraction of this
|
||||
@@ -4381,8 +4328,8 @@ body.picker-page {
|
||||
heading is the wide one. Both are counted in the base step, so the cadence
|
||||
holds while every size in the column moves. */
|
||||
gap: calc(var(--sp-rhythm) * 1.5);
|
||||
padding-block: clamp(14px, 2.4%, 28px);
|
||||
padding-inline: calc(var(--sp-rail) + var(--sp-rail-gap) + 4px) 16px;
|
||||
padding-block: calc(clamp(14px, 2.4%, 28px) + 10px) clamp(14px, 2.4%, 28px);
|
||||
padding-inline: calc(var(--sp-rail) + var(--sp-rail-gap) + 8px) 16px;
|
||||
background: var(--ks-lacquer-raised);
|
||||
color: var(--ks-text);
|
||||
border: 1px solid var(--ks-rule);
|
||||
@@ -5221,6 +5168,7 @@ body.picker-page {
|
||||
|
||||
.picker-preview-type .ps-phone {
|
||||
--ps-gutter: 6%;
|
||||
grid-template-rows: 7.2% 1fr;
|
||||
}
|
||||
|
||||
/* On a structural board the band inset is the drawn field's own margin, and it is
|
||||
@@ -5268,7 +5216,7 @@ body.picker-page {
|
||||
/* The pair is the subject and the image is a placeholder for one, so the copy
|
||||
takes the column and the image takes what it needs to read as a picture. */
|
||||
.picker-preview-type .ps-hero {
|
||||
grid-template-columns: minmax(0, 1fr) 26%;
|
||||
grid-template-columns: minmax(0, 1fr) 34%;
|
||||
gap: var(--ps-gap-lg);
|
||||
padding: var(--ps-band-y) var(--ps-gutter);
|
||||
}
|
||||
@@ -5300,7 +5248,7 @@ body.picker-page {
|
||||
what centers in the band, so a pair that sets a shorter title does not leave
|
||||
the whole section sitting against the rule above it. */
|
||||
.picker-preview-type .ps-editorial {
|
||||
grid-template-columns: minmax(0, 1fr) 38%;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: var(--ps-gap-lg);
|
||||
align-content: center;
|
||||
align-items: start;
|
||||
@@ -5315,6 +5263,16 @@ body.picker-page {
|
||||
height: auto;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--ps-gap-md);
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.picker-preview-type .ps-desktop .ps-gallery-item {
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.picker-preview-type .ps-desktop .ps-gallery-item span {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* The thumbnail stands in for a picture, not for the band: left to fill the
|
||||
@@ -5332,6 +5290,10 @@ body.picker-page {
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.picker-preview-type .ps-desktop .ps-gallery-item > i {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Words set taller than bars, and the phone is the narrowest frame they have
|
||||
to fit. Its card row goes: nav, hero, proof and a section of running copy
|
||||
already show every face at both sizes, and the row that was left over is
|
||||
@@ -5359,6 +5321,65 @@ body.picker-page {
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body {
|
||||
grid-template-rows: repeat(5, auto);
|
||||
gap: 0;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .pt-headline {
|
||||
font-size: 26px;
|
||||
line-height: 1.15;
|
||||
margin-block-end: 8px;
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .pt-body {
|
||||
margin-block-end: var(--ps-gap-md);
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-actions {
|
||||
margin-block-end: var(--ps-gap-lg);
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof {
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
column-gap: var(--ps-gap-md);
|
||||
margin-block-end: var(--ps-gap-md);
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof-divider,
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof-item:nth-of-type(2) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof-item:first-of-type {
|
||||
grid-column: 2;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::before,
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::after {
|
||||
content: "";
|
||||
position: static;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: none;
|
||||
border-left: 1px solid color-mix(in oklab, var(--pvs-bars) 38%, transparent);
|
||||
border-bottom: 1px solid color-mix(in oklab, var(--pvs-bars) 38%, transparent);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::before {
|
||||
grid-column: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-proof::after {
|
||||
grid-column: 3;
|
||||
transform: rotate(-135deg);
|
||||
}
|
||||
|
||||
.picker-preview-type[data-surface="persuade"] .ps-phone-body .ps-editorial-copy {
|
||||
gap: var(--ps-gap-xs);
|
||||
}
|
||||
|
||||
/* One gap owns the spacing down the phone. The margins underneath it were a
|
||||
@@ -5736,7 +5757,9 @@ body.picker-page {
|
||||
}
|
||||
|
||||
.picker-preview-type .ps-gallery-item span {
|
||||
gap: var(--ps-gap-xs);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Card labels wrap instead of truncating: a two-line label still reads, and a
|
||||
@@ -5745,10 +5768,18 @@ body.picker-page {
|
||||
overflow: visible;
|
||||
color: var(--pvs-bars);
|
||||
white-space: normal;
|
||||
text-box: unset;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.picker-preview-type .ps-gallery-item b:first-child {
|
||||
.picker-preview-type .ps-gallery-item b:first-child,
|
||||
.picker-preview-type .ps-gallery-item [data-type-gallery-title] {
|
||||
color: var(--pvs-title);
|
||||
line-height: 1.38;
|
||||
}
|
||||
|
||||
.picker-preview-type .ps-gallery-item b:last-child {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
Reference in New Issue
Block a user