Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
/**
|
|
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
|
2024-01-16 16:58:49 +00:00
|
|
|
* SPDX-FileCopyrightText: 2023 Oriol Carbonell <info@oriolcarbonell.cat>
|
Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
*, *::before, *::after {
|
|
|
|
box-sizing: border-box;
|
|
|
|
}
|
|
|
|
|
Change media picker from <div> to <dialog> and make it modal
Have to call Dialog.showModal when HTMx loaded the dialog in the DOM,
so had to add a onLoad event listened that checks whether the loaded
element is actually a DIALOG.
Had to restrict the margin: 0 for all elements (*) to exclude dialog,
because the browser sets it to auto, and i did not want to set it again
just because i was too overzealous with my “reset”.
The rest of the CSS is just to have a sticky header and footer, and see
the cancel button, that works as a “close”, all the time.
Finally, i realized that if i add the dialog at the end of the fieldset
and let HTMx inherit its hx-target and hx-swap, i no longer need to set
them in the dialog, as HTMx will always replace the fieldset, and i can
have the dialog side by side the current content of the fieldset, that
it was very confusing seeing it disappear when trying to select a new
media.
The cancel button could now just remove the dialog instead of making the
POST, but in local it makes no difference; we’lls see what happens on
production.
2023-09-22 00:11:03 +00:00
|
|
|
*:not(dialog) {
|
Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
|
2023-09-28 00:23:25 +00:00
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 100;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Thin'), url('./fonts/JetBrainsMono-ThinItalic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 200;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono ExtraLight'), url('./fonts/JetBrainsMono-ExtraLightItalic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 300;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Light'), url('./fonts/JetBrainsMono-LightItalic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 400;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono'), url('./fonts/JetBrainsMono-Italic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 500;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Medium'), url('./fonts/JetBrainsMono-MediumItalic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 600;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono SemiBoldItalic'), url('./fonts/JetBrainsMono-SemiBoldItalic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 700;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Bold'), url('./fonts/JetBrainsMono-BoldItalic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: italic;
|
|
|
|
font-weight: 800;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono ExtraBold'), url('./fonts/JetBrainsMono-ExtraBoldItalic.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 100;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Thin'), url('./fonts/JetBrainsMono-Thin.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 200;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono ExtraLight'), url('./fonts/JetBrainsMono-ExtraLight.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 300;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Light'), url('./fonts/JetBrainsMono-Light.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 400;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono'), url('./fonts/JetBrainsMono-Regular.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 500;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Medium'), url('./fonts/JetBrainsMono-Medium.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 600;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono SemiBold'), url('./fonts/JetBrainsMono-SemiBold.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 700;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono Bold'), url('./fonts/JetBrainsMono-Bold.woff2') format('woff2');
|
|
|
|
}
|
|
|
|
|
|
|
|
@font-face {
|
|
|
|
font-family: 'JetBrains Mono';
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 800;
|
|
|
|
font-display: swap;
|
|
|
|
src: local('JetBrains Mono ExtraBold'), url('./fonts/JetBrainsMono-ExtraBold.woff2') format('woff2');
|
Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
html {
|
2023-09-28 00:23:25 +00:00
|
|
|
font-family: 'JetBrains Mono', monospace;
|
Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
font-size: 62.5%;
|
2023-09-28 00:23:25 +00:00
|
|
|
|
|
|
|
--camper--color--black: #3f3b37;
|
|
|
|
--camper--color--dark-gray: #8a8885;
|
|
|
|
--camper--color--light-gray: #e1dbd6;
|
|
|
|
--camper--color--white: #ffffff;
|
|
|
|
--camper--color--yellow: #ffd200;
|
|
|
|
--camper--color--red: #ff7a53;
|
|
|
|
--camper--color--rosy: #ffbaa6;
|
|
|
|
--camper--color--green: #5ae487;
|
|
|
|
--camper--color--light-green: #9fefb9;
|
|
|
|
--camper--color--blue: #55bfff;
|
|
|
|
--camper--color--light-blue: #cbebff;
|
|
|
|
--camper--color--hay: #ffe673;
|
|
|
|
|
|
|
|
--camper--text-color: var(--camper--color--black);
|
|
|
|
--camper--background-color: var(--camper--color--white);
|
|
|
|
|
|
|
|
--camper--header--background-color: #ede9e5;
|
Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
body {
|
|
|
|
font-size: 1.6rem;
|
|
|
|
line-height: 1.5;
|
|
|
|
-webkit-font-smoothing: antialiased;
|
2023-09-28 00:23:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
body, dialog, .media-picker header, .media-picker footer {
|
|
|
|
background-color: var(--camper--background-color);
|
|
|
|
color: var(--camper--text-color);
|
Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
img, picture, video, canvas, svg {
|
|
|
|
display: block;
|
|
|
|
max-width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
input, button, textarea, select {
|
|
|
|
font: inherit;
|
|
|
|
}
|
|
|
|
|
|
|
|
p, h1, h2, h3, h4, h5, h6 {
|
|
|
|
overflow-wrap: break-word;
|
2024-01-15 11:30:13 +00:00
|
|
|
margin: 20px 0;
|
Add the skeleton of the web application
It does nothing more than to server a single page that does nothing
interesting.
This time i do not use a router. Instead, i am trying out a technique
i have seen in an article[0] that i have tried in other, smaller,
projects and seems to work surprisingly well: it just “cuts off” the
URI path by path, passing the request from handler to handler until
it finds its way to a handler that actually serves the request.
That helps to loosen the coupling between the application and lower
handlers, and makes dependencies explicit, because i need to pass the
locale, company, etc. down instead of storing them in contexts. Let’s
see if i do not regret it on a later date.
I also made a lot more packages that in Numerus. In Numerus i actually
only have the single pkg package, and it works, kind of, but i notice
how i name my methods to avoid clashing instead of using packages for
that. That is, instead of pkg.NewApp i now have app.New.
Initially i thought that Locale should be inside app, but then there was
a circular dependency between app and template. That is why i created a
separate package, but now i am wondering if template should be inside
app too, but then i would have app.MustRenderTemplate instead of
template.MustRender.
The CSS is the most bare-bones file i could write because i am focusing
in markup right now; Oriol will fill in the file once the application is
working.
[0]: https://blog.merovius.de/posts/2017-06-18-how-not-to-use-an-http-router/
2023-07-22 22:11:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
:any-link {
|
|
|
|
color: #0000ff;
|
|
|
|
}
|
2023-09-12 18:20:23 +00:00
|
|
|
|
|
|
|
a.missing-translation {
|
|
|
|
color: #ff0000;
|
|
|
|
}
|
Manage all media uploads in a single place
It made no sense to have a file upload in each form that needs a media,
because to reuse an existing media users would need to upload the exact
same file again; this is very unusual and unfriendly.
A better option is to have a “centralized” media section, where people
can upload files there, and then have a picker to select from there.
Ideally, there would be an upload option in the picker, but i did not
add it yet.
I’ve split the content from the media because i want users to have the
option to update a media, for instance when they need to upload a
reduced or cropped version of the same photo, without an edit they would
need to upload the file as a new media and then update all places where
the old version was used. And i did not want to trouble people that
uploads the same photo twice: without the separate relation, doing so
would throw a constraint error.
I do not believe there is any security problem to have all companies
link their media to the same file, as they were already readable by
everyone and could upload the data from a different company to their
own; in other words, it is not worse than it was now.
2023-09-20 23:56:44 +00:00
|
|
|
|
2023-09-28 00:23:25 +00:00
|
|
|
body > a[href="#content"], .sr-only {
|
|
|
|
border: 0;
|
|
|
|
clip: rect(1px, 1px, 1px, 1px);
|
|
|
|
clip-path: inset(50%);
|
|
|
|
height: 1px;
|
|
|
|
margin: -1px;
|
|
|
|
overflow: hidden;
|
|
|
|
padding: 0;
|
|
|
|
position: absolute !important;
|
|
|
|
width: 1px;
|
|
|
|
word-wrap: normal !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
body > a[href="#content"]:focus {
|
|
|
|
background-color: #f1f1f1;
|
|
|
|
border-radius: 3px;
|
|
|
|
box-shadow: 0 0 2px 2px rgba(0, 0, 0, .6);
|
|
|
|
clip: auto !important;
|
|
|
|
clip-path: none;
|
|
|
|
color: #21759b;
|
|
|
|
display: block;
|
|
|
|
font-size: 1.4rem;
|
|
|
|
font-weight: 700;
|
|
|
|
height: auto;
|
|
|
|
left: 5px;
|
|
|
|
line-height: normal;
|
|
|
|
padding: 15px 23px 14px;
|
|
|
|
text-decoration: none;
|
|
|
|
top: 5px;
|
|
|
|
width: auto;
|
|
|
|
z-index: 100000;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* header */
|
2023-09-29 16:20:16 +00:00
|
|
|
body > header {
|
2023-09-28 00:23:25 +00:00
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
background-color: var(--camper--header--background-color);
|
|
|
|
}
|
|
|
|
|
2024-01-21 21:44:04 +00:00
|
|
|
body > header, body > nav ul a {
|
2023-09-28 00:23:25 +00:00
|
|
|
padding: 0 3rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
body > nav {
|
|
|
|
border-bottom: 1px solid var(--camper--color--light-gray);
|
|
|
|
}
|
|
|
|
|
2024-01-21 21:44:04 +00:00
|
|
|
body > nav ul, body > nav ol {
|
2023-09-28 00:23:25 +00:00
|
|
|
display: flex;
|
|
|
|
list-style: none;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
2024-01-21 21:44:04 +00:00
|
|
|
body > nav ul li {
|
2023-09-28 00:23:25 +00:00
|
|
|
flex: 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
body > nav a {
|
|
|
|
text-decoration: none;
|
|
|
|
color: inherit;
|
2024-01-21 21:44:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
body > nav ul a {
|
2023-09-28 00:23:25 +00:00
|
|
|
min-height: 8rem;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
2024-01-21 21:44:04 +00:00
|
|
|
body > .breadcrumb ol {
|
|
|
|
padding: .5em 3rem;
|
|
|
|
gap: .5em;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
body > .breadcrumb li + li::before {
|
|
|
|
content: " > ";
|
|
|
|
}
|
|
|
|
|
2024-01-21 19:50:04 +00:00
|
|
|
body > footer {
|
|
|
|
border-top: 1px solid var(--camper--color--light-gray);
|
|
|
|
padding: .25em 3rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
body > footer p {
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
|
2023-09-28 00:23:25 +00:00
|
|
|
main {
|
|
|
|
padding: 2rem 3rem;
|
|
|
|
}
|
|
|
|
|
2024-02-14 03:54:42 +00:00
|
|
|
/*<editor-fold desc="table">*/
|
|
|
|
|
2023-09-28 00:23:25 +00:00
|
|
|
table:not(.month) {
|
|
|
|
width: 100%;
|
|
|
|
border-collapse: collapse;
|
|
|
|
margin: 2rem 0 0 0;
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:30:13 +00:00
|
|
|
table:not(.month) th, table:not(.month) td {
|
|
|
|
padding: 10px 0;
|
|
|
|
border-bottom: .5px solid;
|
|
|
|
}
|
|
|
|
|
|
|
|
table:not(.month) th {
|
2024-01-18 20:05:30 +00:00
|
|
|
text-align: start;
|
2024-01-15 11:30:13 +00:00
|
|
|
}
|
|
|
|
|
2024-02-14 03:54:42 +00:00
|
|
|
table:not(.month) .numeric, .numeric {
|
|
|
|
text-align: end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*</editor-fold>*/
|
2023-09-28 00:23:25 +00:00
|
|
|
/* user menu */
|
|
|
|
nav details {
|
|
|
|
position: relative;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details summary {
|
|
|
|
background-color: var(--camper--background-color);
|
|
|
|
display: flex;
|
|
|
|
cursor: pointer;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
width: 7rem;
|
|
|
|
height: 7rem;
|
|
|
|
margin: 1rem 0;
|
|
|
|
border-radius: 50%;
|
|
|
|
border: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details summary:hover,
|
|
|
|
nav details summary:focus,
|
|
|
|
nav details a:hover,
|
|
|
|
nav details button:hover {
|
2023-09-28 23:35:05 +00:00
|
|
|
background-color: var(--camper--color--light-gray);
|
2023-09-28 00:23:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nav details summary img {
|
|
|
|
width: 4.8rem;
|
|
|
|
object-fit: contain;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details summary span {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details summary::-webkit-details-marker {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details[open] summary::before {
|
|
|
|
background-color: var(--camper--header--background-color);
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
right: 0;
|
|
|
|
bottom: 0;
|
|
|
|
content: "";
|
|
|
|
cursor: default;
|
|
|
|
z-index: 10;
|
|
|
|
mix-blend-mode: multiply;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details ul {
|
|
|
|
position: absolute;
|
|
|
|
padding: 1rem 2rem;
|
|
|
|
list-style: none;
|
|
|
|
background-color: var(--camper--background-color);
|
|
|
|
z-index: 20;
|
|
|
|
right: -1.875em;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details a:any-link, nav details button {
|
|
|
|
color: var(--camper--text-color);
|
|
|
|
font-size: 2rem;
|
|
|
|
font-style: italic;
|
|
|
|
height: 5.5rem;
|
|
|
|
width: 46rem;
|
|
|
|
padding-left: 2rem;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
border: 0;
|
|
|
|
text-decoration: none;
|
|
|
|
text-transform: initial;
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
nav details li + li {
|
|
|
|
border-top: 1px solid var(--camper--color--dark-gray);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* form */
|
|
|
|
fieldset {
|
|
|
|
border: none;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
form h2 {
|
|
|
|
margin-bottom: 1em;
|
|
|
|
}
|
|
|
|
|
|
|
|
label, legend {
|
|
|
|
display: block;
|
|
|
|
font-style: italic;
|
|
|
|
}
|
|
|
|
|
2024-01-26 21:27:54 +00:00
|
|
|
fieldset + label, fieldset + fieldset, label + label:not([x-show]), label + fieldset {
|
2023-09-28 00:23:25 +00:00
|
|
|
margin-top: 1rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
label input, label textarea, label select {
|
|
|
|
font-style: normal;
|
|
|
|
}
|
|
|
|
|
|
|
|
form fieldset + footer {
|
|
|
|
margin-top: 3rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
input[type="submit"], button, .button {
|
|
|
|
min-width: 34rem;
|
|
|
|
background-color: var(--camper--color--white);
|
|
|
|
border: 2px solid var(--camper--color--black);
|
|
|
|
text-transform: uppercase;
|
|
|
|
display: inline-block;
|
|
|
|
text-align: center;
|
|
|
|
padding: 1rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
input[type="text"],
|
|
|
|
input[type="search"],
|
|
|
|
input[type="password"],
|
|
|
|
input[type="email"],
|
|
|
|
input[type="tel"],
|
|
|
|
input[type="url"],
|
|
|
|
input[type="number"],
|
|
|
|
input[type="date"],
|
|
|
|
select,
|
|
|
|
textarea {
|
|
|
|
background-color: var(--camper--background-color);
|
|
|
|
border: 1px solid var(--camper--color--black);
|
|
|
|
border-radius: 0;
|
|
|
|
padding: .5rem 1rem;
|
|
|
|
height: 3.5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* login */
|
|
|
|
#login-form {
|
|
|
|
background-color: var(--camper--color--hay);
|
|
|
|
padding: 1.25em;
|
|
|
|
}
|
|
|
|
|
|
|
|
#login-form fieldset {
|
|
|
|
display: flex;
|
|
|
|
gap: 2rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
#login-form label {
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* media */
|
Manage all media uploads in a single place
It made no sense to have a file upload in each form that needs a media,
because to reuse an existing media users would need to upload the exact
same file again; this is very unusual and unfriendly.
A better option is to have a “centralized” media section, where people
can upload files there, and then have a picker to select from there.
Ideally, there would be an upload option in the picker, but i did not
add it yet.
I’ve split the content from the media because i want users to have the
option to update a media, for instance when they need to upload a
reduced or cropped version of the same photo, without an edit they would
need to upload the file as a new media and then update all places where
the old version was used. And i did not want to trouble people that
uploads the same photo twice: without the separate relation, doing so
would throw a constraint error.
I do not believe there is any security problem to have all companies
link their media to the same file, as they were already readable by
everyone and could upload the data from a different company to their
own; in other words, it is not worse than it was now.
2023-09-20 23:56:44 +00:00
|
|
|
.media-grid {
|
|
|
|
display: grid;
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(26rem, 1fr));
|
|
|
|
grid-auto-rows: 1fr;
|
|
|
|
list-style: none;
|
|
|
|
gap: 1rem;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.media-grid img, .media-grid button {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
max-height: 26rem;
|
|
|
|
}
|
|
|
|
|
2023-09-28 00:23:25 +00:00
|
|
|
.media-grid button {
|
|
|
|
min-width: 0;
|
|
|
|
border: none;
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
|
Manage all media uploads in a single place
It made no sense to have a file upload in each form that needs a media,
because to reuse an existing media users would need to upload the exact
same file again; this is very unusual and unfriendly.
A better option is to have a “centralized” media section, where people
can upload files there, and then have a picker to select from there.
Ideally, there would be an upload option in the picker, but i did not
add it yet.
I’ve split the content from the media because i want users to have the
option to update a media, for instance when they need to upload a
reduced or cropped version of the same photo, without an edit they would
need to upload the file as a new media and then update all places where
the old version was used. And i did not want to trouble people that
uploads the same photo twice: without the separate relation, doing so
would throw a constraint error.
I do not believe there is any security problem to have all companies
link their media to the same file, as they were already readable by
everyone and could upload the data from a different company to their
own; in other words, it is not worse than it was now.
2023-09-20 23:56:44 +00:00
|
|
|
.media-grid img {
|
|
|
|
object-fit: cover;
|
|
|
|
}
|
Change media picker from <div> to <dialog> and make it modal
Have to call Dialog.showModal when HTMx loaded the dialog in the DOM,
so had to add a onLoad event listened that checks whether the loaded
element is actually a DIALOG.
Had to restrict the margin: 0 for all elements (*) to exclude dialog,
because the browser sets it to auto, and i did not want to set it again
just because i was too overzealous with my “reset”.
The rest of the CSS is just to have a sticky header and footer, and see
the cancel button, that works as a “close”, all the time.
Finally, i realized that if i add the dialog at the end of the fieldset
and let HTMx inherit its hx-target and hx-swap, i no longer need to set
them in the dialog, as HTMx will always replace the fieldset, and i can
have the dialog side by side the current content of the fieldset, that
it was very confusing seeing it disappear when trying to select a new
media.
The cancel button could now just remove the dialog instead of making the
POST, but in local it makes no difference; we’lls see what happens on
production.
2023-09-22 00:11:03 +00:00
|
|
|
|
|
|
|
.media-picker {
|
|
|
|
min-width: 75vw;
|
|
|
|
}
|
|
|
|
|
|
|
|
.media-picker header, .media-picker footer {
|
|
|
|
position: sticky;
|
|
|
|
padding-top: 1rem;
|
|
|
|
padding-bottom: 1rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.media-picker header {
|
|
|
|
top: -1em;
|
|
|
|
}
|
|
|
|
|
|
|
|
.media-picker footer {
|
|
|
|
bottom: -1em;
|
|
|
|
}
|
Add campsite map in SVG
I intend to use the same SVG file for customers and employees, so i had
to change Oriol’s design to add a class to layers that are supposed to
be only for customers, like trees. These are hidden in the admin area.
I understood that customers and employees have to click on a campsite to
select it, and then they can book or whatever they need to do to them.
Since customers and employees most certainly will need to have different
listeners on campsites, i decided to add the link with JavaScript. To
do so, i need a custom XML attribute with the campsite’s identifier.
Since i have seen that all campsites have a label, i changed the
“identifier” to the unique combination (company_id, label). The
company_id is there because different companies could have the same
label; i left the campsite_id primary key for foreign constraints.
In this case, as a test, i add an <a> element to the campsite with a
link to edit it; we’ll discuss with Oriol what exactly it needs to do.
However, the original design had the labels in a different layer, that
interfered with the link, as the numbers must be above the path and
the link must wrap the path in order to “inherit” its shape. I had no
other recourse than to move the labels in the same layer as the paths’.
2023-09-24 01:17:13 +00:00
|
|
|
|
2024-01-24 13:42:47 +00:00
|
|
|
/*<editor-fold desc="Campground Map">*/
|
|
|
|
|
|
|
|
#campground_map .guest-only, #arbres, #zones {
|
Add campsite map in SVG
I intend to use the same SVG file for customers and employees, so i had
to change Oriol’s design to add a class to layers that are supposed to
be only for customers, like trees. These are hidden in the admin area.
I understood that customers and employees have to click on a campsite to
select it, and then they can book or whatever they need to do to them.
Since customers and employees most certainly will need to have different
listeners on campsites, i decided to add the link with JavaScript. To
do so, i need a custom XML attribute with the campsite’s identifier.
Since i have seen that all campsites have a label, i changed the
“identifier” to the unique combination (company_id, label). The
company_id is there because different companies could have the same
label; i left the campsite_id primary key for foreign constraints.
In this case, as a test, i add an <a> element to the campsite with a
link to edit it; we’ll discuss with Oriol what exactly it needs to do.
However, the original design had the labels in a different layer, that
interfered with the link, as the numbers must be above the path and
the link must wrap the path in order to “inherit” its shape. I had no
other recourse than to move the labels in the same layer as the paths’.
2023-09-24 01:17:13 +00:00
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
Fill the immediate path inside map’s anchors, and remove one extra group
In the map i added in e3503187d, paths around each accommodation
inherited the fill and stroke from the group, thus i could just override
that fill at the anchor level, but the current map sets the fill to each
accommodation’s path, party because the text is not a path too, partly
because Affinity is a visual tool only and does not give a shit about
mark up.
If we keep the text in a group, however, we can set the fill of the area
using CSS too, although it is not nice due to `!important`, but still.
There was a plot, however, #93, that had the area in a group too, and
i had to remove that group manually.
2024-01-25 00:26:02 +00:00
|
|
|
#campground_map a:hover > path {
|
|
|
|
fill: var(--camper--color--hay) !important;
|
Add campsite map in SVG
I intend to use the same SVG file for customers and employees, so i had
to change Oriol’s design to add a class to layers that are supposed to
be only for customers, like trees. These are hidden in the admin area.
I understood that customers and employees have to click on a campsite to
select it, and then they can book or whatever they need to do to them.
Since customers and employees most certainly will need to have different
listeners on campsites, i decided to add the link with JavaScript. To
do so, i need a custom XML attribute with the campsite’s identifier.
Since i have seen that all campsites have a label, i changed the
“identifier” to the unique combination (company_id, label). The
company_id is there because different companies could have the same
label; i left the campsite_id primary key for foreign constraints.
In this case, as a test, i add an <a> element to the campsite with a
link to edit it; we’ll discuss with Oriol what exactly it needs to do.
However, the original design had the labels in a different layer, that
interfered with the link, as the numbers must be above the path and
the link must wrap the path in order to “inherit” its shape. I had no
other recourse than to move the labels in the same layer as the paths’.
2023-09-24 01:17:13 +00:00
|
|
|
}
|
2023-09-25 18:10:33 +00:00
|
|
|
|
2024-01-24 13:42:47 +00:00
|
|
|
/*</editor-fold>*/
|
|
|
|
|
2023-09-25 18:10:33 +00:00
|
|
|
[class^="icon_"] {
|
|
|
|
background-size: 2em 2em;
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-position: center left;
|
|
|
|
}
|
|
|
|
|
|
|
|
.services [class^="icon_"] {
|
|
|
|
padding-left: 2.5em;
|
|
|
|
}
|
|
|
|
|
|
|
|
.icon-input ul {
|
|
|
|
padding: 0;
|
|
|
|
list-style: none;
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
gap: .25em;
|
|
|
|
}
|
|
|
|
|
|
|
|
.icon-input button {
|
2024-01-23 23:40:19 +00:00
|
|
|
min-width: 0;
|
|
|
|
width: 4rem;
|
|
|
|
height: 4rem;
|
|
|
|
background-position: center;
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
|
|
|
|
2024-01-24 18:35:38 +00:00
|
|
|
.icon-input button[aria-pressed="true"], .lang-selector button[aria-pressed="true"] {
|
|
|
|
background-color: var(--camper--color--hay);
|
2023-09-25 18:10:33 +00:00
|
|
|
}
|
2023-09-27 00:23:09 +00:00
|
|
|
|
2024-01-16 16:37:40 +00:00
|
|
|
/* accommodation type */
|
|
|
|
|
|
|
|
fieldset img {
|
2024-01-18 20:05:30 +00:00
|
|
|
max-width: 400px;
|
2024-01-16 16:37:40 +00:00
|
|
|
}
|
|
|
|
|
2023-09-29 16:20:16 +00:00
|
|
|
/* calendar */
|
|
|
|
.season-calendar button {
|
|
|
|
display: flex;
|
|
|
|
gap: 1em;
|
|
|
|
border: none;
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar form button:first-child, .season-calendar > header button {
|
|
|
|
min-width: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar > header {
|
|
|
|
display: flex;
|
|
|
|
gap: 2rem;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar > header button:first-of-type {
|
|
|
|
order: -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar > header button:first-of-type::before {
|
|
|
|
content: "←";
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar > header button:last-of-type::before {
|
|
|
|
content: "→";
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar > div {
|
2023-09-27 00:23:09 +00:00
|
|
|
display: grid;
|
|
|
|
grid-template-columns: repeat(3, auto);
|
2023-09-27 12:21:27 +00:00
|
|
|
grid-auto-rows: 1fr;
|
2023-09-27 00:23:09 +00:00
|
|
|
justify-content: center;
|
2023-09-27 12:21:27 +00:00
|
|
|
align-items: start;
|
2023-09-27 00:23:09 +00:00
|
|
|
gap: 2em;
|
|
|
|
}
|
|
|
|
|
2023-09-27 12:21:27 +00:00
|
|
|
@media (max-width: 48rem) {
|
2023-09-29 16:20:16 +00:00
|
|
|
.season-calendar > div {
|
2023-09-27 12:21:27 +00:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
2023-09-29 16:20:16 +00:00
|
|
|
|
2023-09-28 23:35:05 +00:00
|
|
|
.season-calendar table {
|
|
|
|
width: 100%;
|
|
|
|
}
|
2023-09-27 12:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar table {
|
|
|
|
border-collapse: collapse;
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar td {
|
|
|
|
width: calc(100% / 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar time {
|
|
|
|
display: block;
|
|
|
|
width: 100%;
|
|
|
|
min-width: 3rem;
|
|
|
|
aspect-ratio: 1;
|
2024-01-15 11:30:13 +00:00
|
|
|
text-indent: 20%;
|
2023-09-27 12:21:27 +00:00
|
|
|
white-space: nowrap;
|
2024-01-15 11:30:13 +00:00
|
|
|
overflow: visible;
|
|
|
|
padding: 3px 0 0 0;
|
2023-09-27 12:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar [aria-checked] {
|
2023-09-28 00:23:25 +00:00
|
|
|
border: 2px solid var(--camper--color--black);
|
2023-09-27 12:21:27 +00:00
|
|
|
position: relative;
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar [aria-checked]::after {
|
|
|
|
content: "";
|
|
|
|
position: absolute;
|
|
|
|
top: 50%;
|
|
|
|
left: 50%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
display: block;
|
2023-09-28 00:23:25 +00:00
|
|
|
background-color: var(--camper--color--black);
|
|
|
|
border-radius: 50%;
|
2023-09-27 12:21:27 +00:00
|
|
|
width: .8rem;
|
|
|
|
height: .8rem;
|
|
|
|
}
|
|
|
|
|
2023-09-28 23:35:05 +00:00
|
|
|
.season-calendar form button:first-child {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
right: 0;
|
|
|
|
background-color: transparent;
|
|
|
|
}
|
|
|
|
|
2023-09-29 16:20:16 +00:00
|
|
|
.season-calendar form button:hover, .season-calendar form button:first-child:hover {
|
2023-09-28 23:35:05 +00:00
|
|
|
background-color: var(--camper--color--hay);
|
|
|
|
}
|
|
|
|
|
|
|
|
.season-calendar form button:first-child::before {
|
|
|
|
content: "✕";
|
2023-09-27 12:21:27 +00:00
|
|
|
}
|
|
|
|
|
2023-12-20 18:52:14 +00:00
|
|
|
.sortable tbody tr td:first-child {
|
|
|
|
display: flex;
|
2024-01-15 11:30:13 +00:00
|
|
|
min-height: 70px;
|
|
|
|
align-items: center;
|
2023-12-20 18:52:14 +00:00
|
|
|
}
|
|
|
|
|
2024-01-18 02:27:07 +00:00
|
|
|
.sortable img {
|
2024-01-18 20:05:30 +00:00
|
|
|
max-width: 20rem;
|
|
|
|
border-radius: 5px;
|
2024-01-18 02:27:07 +00:00
|
|
|
}
|
|
|
|
|
2023-12-20 18:52:14 +00:00
|
|
|
#slide-index img {
|
|
|
|
width: 192px;
|
|
|
|
aspect-ratio: 4 / 3;
|
|
|
|
object-fit: cover;
|
|
|
|
}
|
|
|
|
|
|
|
|
.sortable .handle {
|
|
|
|
display: inline-block;
|
|
|
|
width: 1.5em;
|
|
|
|
aspect-ratio: 1;
|
|
|
|
cursor: grab;
|
|
|
|
background: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"%3E%3Crect width="256" height="256" fill="none"/%3E%3Ccircle cx="92" cy="60" r="12"/%3E%3Ccircle cx="164" cy="60" r="12"/%3E%3Ccircle cx="92" cy="128" r="12"/%3E%3Ccircle cx="164" cy="128" r="12"/%3E%3Ccircle cx="92" cy="196" r="12"/%3E%3Ccircle cx="164" cy="196" r="12"/%3E%3C/svg%3E') left center no-repeat;
|
|
|
|
}
|
|
|
|
|
|
|
|
.sortable-ghost {
|
|
|
|
background-color: #aaa;
|
|
|
|
}
|
|
|
|
|
2023-09-28 00:23:25 +00:00
|
|
|
/* snack bar */
|
2023-09-28 23:35:05 +00:00
|
|
|
#snackbar [role="alert"] {
|
2023-09-28 00:23:25 +00:00
|
|
|
cursor: pointer;
|
|
|
|
background-color: var(--camper--color--black);
|
|
|
|
color: var(--camper--color--white);
|
|
|
|
padding: 2rem;
|
|
|
|
min-width: 28.8rem;
|
|
|
|
max-width: 56.8rem;
|
|
|
|
border-radius: 2px;
|
|
|
|
position: fixed;
|
|
|
|
translate: -50% 100%;
|
|
|
|
left: 50%;
|
|
|
|
bottom: 0;
|
|
|
|
transition: translate;
|
|
|
|
transition-duration: 300ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
#snackbar [role="alert"].open {
|
|
|
|
translate: -50%;
|
|
|
|
}
|
2023-12-21 15:19:04 +00:00
|
|
|
|
2024-01-18 20:05:30 +00:00
|
|
|
/*<editor-fold desc="i18n input">*/
|
|
|
|
|
2023-12-21 20:17:04 +00:00
|
|
|
[x-cloak] {
|
|
|
|
display: none !important;
|
|
|
|
}
|
|
|
|
|
|
|
|
.lang-selector {
|
|
|
|
display: flex;
|
|
|
|
gap: .25em;
|
2024-01-15 11:30:13 +00:00
|
|
|
margin: 0 0 5px 0;
|
2023-12-21 15:19:04 +00:00
|
|
|
}
|
|
|
|
|
2023-12-21 20:17:04 +00:00
|
|
|
.lang-selector button {
|
|
|
|
min-width: auto;
|
|
|
|
padding: .15em;
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
|
2023-12-22 03:12:03 +00:00
|
|
|
label[x-show] > span, label[x-show] > br {
|
2023-12-21 20:17:04 +00:00
|
|
|
display: none;
|
|
|
|
}
|
2024-01-18 20:05:30 +00:00
|
|
|
|
|
|
|
/*</editor-fold>*/
|
2024-02-14 03:54:42 +00:00
|
|
|
/*<editor-fold desc="statuses">*/
|
2024-01-18 20:05:30 +00:00
|
|
|
|
2024-02-14 03:54:42 +00:00
|
|
|
.booking-created .booking-status,
|
2024-02-15 14:17:21 +00:00
|
|
|
.payment-pending .payment-status {
|
2024-01-18 20:05:30 +00:00
|
|
|
background-color: var(--camper--color--light-blue);
|
|
|
|
}
|
|
|
|
|
2024-02-14 03:54:42 +00:00
|
|
|
.booking-cancelled .booking-status,
|
|
|
|
.payment-failed .payment-status {
|
2024-01-18 20:05:30 +00:00
|
|
|
background-color: var(--camper--color--rosy);
|
|
|
|
}
|
|
|
|
|
2024-02-14 03:54:42 +00:00
|
|
|
.booking-confirmed .booking-status,
|
2024-02-15 14:17:21 +00:00
|
|
|
.payment-preauth .payment-status {
|
2024-01-18 20:05:30 +00:00
|
|
|
background-color: var(--camper--color--hay);
|
|
|
|
}
|
|
|
|
|
2024-02-14 03:54:42 +00:00
|
|
|
.booking-checked-in .booking-status,
|
2024-02-15 14:17:21 +00:00
|
|
|
.payment-completed .payment-status {
|
2024-01-18 20:05:30 +00:00
|
|
|
background-color: var(--camper--color--light-green);
|
|
|
|
}
|
|
|
|
|
2024-02-14 03:54:42 +00:00
|
|
|
.booking-invoiced .booking-status,
|
2024-02-15 14:17:21 +00:00
|
|
|
.payment-refunded .payment-status,
|
|
|
|
.payment-draft .payment-status,
|
|
|
|
.payment-voided .payment-status {
|
2024-01-18 20:05:30 +00:00
|
|
|
background-color: var(--camper--color--light-gray);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*</editor-fold>*/
|
2024-03-24 21:06:59 +00:00
|
|
|
/*<editor-fold desc="Payment">*/
|
|
|
|
|
|
|
|
#payment-heading {
|
|
|
|
float: left;
|
|
|
|
}
|
|
|
|
|
|
|
|
#payment-actions {
|
2024-03-25 15:43:58 +00:00
|
|
|
display: flex;
|
2024-03-24 21:06:59 +00:00
|
|
|
gap: 2ch;
|
|
|
|
justify-content: end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*</editor-fold>*/
|
2024-04-19 09:29:43 +00:00
|
|
|
/*<editor-fold desc="Campsites Booking">*/
|
|
|
|
|
|
|
|
#campsites-booking {
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
max-height: 90vh;
|
2024-04-19 09:29:43 +00:00
|
|
|
overflow: scroll;
|
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking table {
|
|
|
|
width: auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking .weekend {
|
|
|
|
background-color: var(--camper--color--rosy);
|
|
|
|
}
|
|
|
|
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
#campsites-booking .today {
|
|
|
|
border-left: 2px solid red;
|
|
|
|
}
|
|
|
|
|
2024-04-19 09:29:43 +00:00
|
|
|
#campsites-booking colgroup {
|
|
|
|
border-right: 2px solid;
|
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking col {
|
|
|
|
border: 1px solid;
|
|
|
|
min-width: 2.25ch;
|
|
|
|
max-width: 2.25ch;
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
width: 2.25ch;
|
2024-04-19 09:29:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking th {
|
|
|
|
background-color: var(--camper--background-color);
|
|
|
|
background-clip: padding-box;
|
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking thead tr:first-child th, #campsites-booking tbody th {
|
|
|
|
padding: 0 .5ch;
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
max-width: calc(2.25ch * var(--days));
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
2024-04-19 09:29:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking thead tr:last-child th {
|
|
|
|
padding: 0;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
2024-04-19 19:09:28 +00:00
|
|
|
#campsites-booking thead,
|
|
|
|
#campsites-booking tbody th {
|
2024-04-19 09:29:43 +00:00
|
|
|
position: sticky;
|
2024-04-19 19:09:28 +00:00
|
|
|
z-index: 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking thead {
|
2024-04-19 09:29:43 +00:00
|
|
|
top: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking tbody th {
|
|
|
|
left: 0;
|
|
|
|
}
|
|
|
|
|
2024-04-19 19:09:28 +00:00
|
|
|
#campsites-booking tbody td {
|
|
|
|
position: relative;
|
Allow opening a booking or creating a new from the booking grid
I wanted to use a regular <a>, but apparently rendering that many
anchors is too resource-intensive for Firefox, and it is noticeably
slower. It was even worse, in fact, because i had to have different
content for the main grid and the grid show in the new booking form,
as i did not want to have these links there, and had call a template for
each cell: 3 months × ~30 days × ~100 campsites = 9000 calls!
Using JavaScript for that is shameful, but it does not add much to the
existing markup, and no need for template fuckery.
I am using double-click to follow these links, instead of single click,
because it would be too easy to misclik on the grid, but that forced me
to add `user-select: none` to prevent the selection of text when double-
clicking.
2024-04-25 10:29:43 +00:00
|
|
|
user-select: none;
|
2024-04-19 19:09:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#campsites-booking tbody div {
|
|
|
|
border: 1px solid;
|
|
|
|
position: absolute;
|
|
|
|
z-index: 5;
|
|
|
|
top: 0;
|
|
|
|
left: calc(2.25ch * var(--booking-begin, 0) / 2);
|
|
|
|
bottom: 0;
|
|
|
|
white-space: nowrap;
|
|
|
|
overflow: hidden;
|
|
|
|
width: calc(2.25ch * (var(--booking-nights, 1) + (var(--booking-end, 0) - var(--booking-begin, 0)) / 2) - 1px);
|
|
|
|
}
|
|
|
|
|
2024-04-19 09:29:43 +00:00
|
|
|
#booking-filter, #booking-filter fieldset {
|
|
|
|
display: flex;
|
|
|
|
gap: 1ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-filter label, #booking-filter fieldset + fieldset, #booking-filter footer {
|
|
|
|
margin-top: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-filter input[type="number"] {
|
|
|
|
width: 9ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-filter footer {
|
|
|
|
align-self: end;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-filter button {
|
|
|
|
min-width: unset;
|
|
|
|
padding: .25em 1em;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*</editor-fold>*/
|
“Mockup” for the new booking form
It does nothing but compute the total of a booking, much like it does
for guests. In fact, i use the same payment relations to do the exact
same computation, otherwise i am afraid i will make a mistake in the
ACSI or such, now or in future version; better if both are exactly the
same.
The idea is that once the user creates the booking, i will delete that
payment, because it makes no sense to keep it in this case; nobody is
going to pay for it.
Had to reuse the grid showing the bookings of campsites because
employees need to select one or more campsites to book, and need to see
which are available. In this case, i have to filter by campsite type
and use the arrival and departure dates to filter the months, now up to
the day, not just month.
Had to change max width of th and td in the grid to take into account
that now a month could have a single day, for instance, and the month
heading can not stretch the day or booking spans would not be in their
correct positions.
For that, i needed to access campsiteEntry, bookingEntry, and Month from
campsite package, but campsite imports campsite/types, and
campsite/types already imports booking for the BookingDates type. To
break the cycle, had to move all that to booking and use from campsite;
it is mostly unchanged, except for the granularity of dates up to days
instead of just months.
The design of this form calls for a different way of showing the totals,
because here employees have to see the amount next to the input with
the units, instead of having a footer with the table. I did not like
the idea of having to query the database for that, therefore i “lifter”
the payment draft into a struct that both public and admin forms use
to show they respective views of the cart.
2024-04-23 19:07:41 +00:00
|
|
|
/*<editor-fold desc="Booking Form">*/
|
|
|
|
|
|
|
|
#booking-form > fieldset {
|
|
|
|
display: grid;
|
|
|
|
gap: .5em;
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form fieldset fieldset, #booking-form .colspan {
|
|
|
|
grid-column: span 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form #campsites-booking {
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form :is(label, fieldset fieldset),
|
|
|
|
#booking-form .booking-items table {
|
|
|
|
margin-top: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form :is(input:not([type='checkbox']), select) {
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form :is(.customer-details, .booking-period) {
|
|
|
|
display: grid;
|
|
|
|
gap: .5em;
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
align-content: start;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form .booking-items td {
|
|
|
|
padding: .3125rem 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form .booking-items br {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form .booking-items td:first-child input {
|
|
|
|
width: 6ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
#booking-form h3 {
|
|
|
|
margin-bottom: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*</editor-fold>*/
|