+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wco/privatri/message.mustache b/wco/privatri/message.mustache
new file mode 100644
index 0000000..9aa563f
--- /dev/null
+++ b/wco/privatri/message.mustache
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/wco/privatri/output.css b/wco/privatri/output.css
new file mode 100644
index 0000000..e25d2bc
--- /dev/null
+++ b/wco/privatri/output.css
@@ -0,0 +1,2733 @@
+/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */
+@layer properties;
+@layer theme, base, components, utilities;
+@layer theme {
+ :root, :host {
+ --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
+ "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
+ "Courier New", monospace;
+ --spacing: 0.25rem;
+ --container-xs: 20rem;
+ --text-sm: 0.875rem;
+ --text-sm--line-height: calc(1.25 / 0.875);
+ --text-base: 1rem;
+ --text-base--line-height: calc(1.5 / 1);
+ --text-lg: 1.125rem;
+ --text-lg--line-height: calc(1.75 / 1.125);
+ --text-xl: 1.25rem;
+ --text-xl--line-height: calc(1.75 / 1.25);
+ --text-4xl: 2.25rem;
+ --text-4xl--line-height: calc(2.5 / 2.25);
+ --font-weight-medium: 500;
+ --font-weight-semibold: 600;
+ --radius-lg: 0.5rem;
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
+ --default-transition-duration: 150ms;
+ --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ --default-font-family: var(--font-sans);
+ --default-mono-font-family: var(--font-mono);
+ }
+}
+@layer base {
+ *, ::after, ::before, ::backdrop, ::file-selector-button {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ border: 0 solid;
+ }
+ html, :host {
+ line-height: 1.5;
+ -webkit-text-size-adjust: 100%;
+ tab-size: 4;
+ font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
+ font-feature-settings: var(--default-font-feature-settings, normal);
+ font-variation-settings: var(--default-font-variation-settings, normal);
+ -webkit-tap-highlight-color: transparent;
+ }
+ hr {
+ height: 0;
+ color: inherit;
+ border-top-width: 1px;
+ }
+ abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+ }
+ h1, h2, h3, h4, h5, h6 {
+ font-size: inherit;
+ font-weight: inherit;
+ }
+ a {
+ color: inherit;
+ -webkit-text-decoration: inherit;
+ text-decoration: inherit;
+ }
+ b, strong {
+ font-weight: bolder;
+ }
+ code, kbd, samp, pre {
+ font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
+ font-feature-settings: var(--default-mono-font-feature-settings, normal);
+ font-variation-settings: var(--default-mono-font-variation-settings, normal);
+ font-size: 1em;
+ }
+ small {
+ font-size: 80%;
+ }
+ sub, sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+ }
+ sub {
+ bottom: -0.25em;
+ }
+ sup {
+ top: -0.5em;
+ }
+ table {
+ text-indent: 0;
+ border-color: inherit;
+ border-collapse: collapse;
+ }
+ :-moz-focusring {
+ outline: auto;
+ }
+ progress {
+ vertical-align: baseline;
+ }
+ summary {
+ display: list-item;
+ }
+ ol, ul, menu {
+ list-style: none;
+ }
+ img, svg, video, canvas, audio, iframe, embed, object {
+ display: block;
+ vertical-align: middle;
+ }
+ img, video {
+ max-width: 100%;
+ height: auto;
+ }
+ button, input, select, optgroup, textarea, ::file-selector-button {
+ font: inherit;
+ font-feature-settings: inherit;
+ font-variation-settings: inherit;
+ letter-spacing: inherit;
+ color: inherit;
+ border-radius: 0;
+ background-color: transparent;
+ opacity: 1;
+ }
+ :where(select:is([multiple], [size])) optgroup {
+ font-weight: bolder;
+ }
+ :where(select:is([multiple], [size])) optgroup option {
+ padding-inline-start: 20px;
+ }
+ ::file-selector-button {
+ margin-inline-end: 4px;
+ }
+ ::placeholder {
+ opacity: 1;
+ }
+ @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
+ ::placeholder {
+ color: currentcolor;
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, currentcolor 50%, transparent);
+ }
+ }
+ }
+ textarea {
+ resize: vertical;
+ }
+ ::-webkit-search-decoration {
+ -webkit-appearance: none;
+ }
+ ::-webkit-date-and-time-value {
+ min-height: 1lh;
+ text-align: inherit;
+ }
+ ::-webkit-datetime-edit {
+ display: inline-flex;
+ }
+ ::-webkit-datetime-edit-fields-wrapper {
+ padding: 0;
+ }
+ ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
+ padding-block: 0;
+ }
+ :-moz-ui-invalid {
+ box-shadow: none;
+ }
+ button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
+ appearance: button;
+ }
+ ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
+ height: auto;
+ }
+ [hidden]:where(:not([hidden="until-found"])) {
+ display: none !important;
+ }
+}
+@layer utilities {
+ .drawer-side {
+ pointer-events: none;
+ visibility: hidden;
+ position: fixed;
+ inset-inline-start: calc(0.25rem * 0);
+ top: calc(0.25rem * 0);
+ z-index: 1;
+ grid-column-start: 1;
+ grid-row-start: 1;
+ display: grid;
+ width: 100%;
+ grid-template-columns: repeat(1, minmax(0, 1fr));
+ grid-template-rows: repeat(1, minmax(0, 1fr));
+ align-items: flex-start;
+ justify-items: start;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ overscroll-behavior: contain;
+ opacity: 0%;
+ transition: opacity 0.2s ease-out 0.1s allow-discrete, visibility 0.3s ease-out 0.1s allow-discrete;
+ height: 100vh;
+ height: 100dvh;
+ > .drawer-overlay {
+ position: sticky;
+ top: calc(0.25rem * 0);
+ cursor: pointer;
+ place-self: stretch;
+ background-color: oklch(0% 0 0 / 40%);
+ }
+ > * {
+ grid-column-start: 1;
+ grid-row-start: 1;
+ }
+ > *:not(.drawer-overlay) {
+ will-change: transform;
+ transition: translate 0.3s ease-out;
+ translate: -100%;
+ [dir="rtl"] & {
+ translate: 100%;
+ }
+ }
+ }
+ .drawer-open {
+ > .drawer-side {
+ overflow-y: auto;
+ }
+ > .drawer-toggle {
+ display: none;
+ & ~ .drawer-side {
+ pointer-events: auto;
+ visibility: visible;
+ position: sticky;
+ display: block;
+ width: auto;
+ overscroll-behavior: auto;
+ opacity: 100%;
+ & > .drawer-overlay {
+ cursor: default;
+ background-color: transparent;
+ }
+ & > *:not(.drawer-overlay) {
+ translate: 0%;
+ [dir="rtl"] & {
+ translate: 0%;
+ }
+ }
+ }
+ &:checked ~ .drawer-side {
+ pointer-events: auto;
+ visibility: visible;
+ }
+ }
+ }
+ .drawer-toggle {
+ position: fixed;
+ height: calc(0.25rem * 0);
+ width: calc(0.25rem * 0);
+ appearance: none;
+ opacity: 0%;
+ &:checked {
+ & ~ .drawer-side {
+ pointer-events: auto;
+ visibility: visible;
+ overflow-y: auto;
+ opacity: 100%;
+ & > *:not(.drawer-overlay) {
+ translate: 0%;
+ }
+ }
+ }
+ &:focus-visible ~ .drawer-content label.drawer-button {
+ outline: 2px solid;
+ outline-offset: 2px;
+ }
+ }
+ .menu {
+ display: flex;
+ width: fit-content;
+ flex-direction: column;
+ flex-wrap: wrap;
+ padding: calc(0.25rem * 2);
+ --menu-active-fg: var(--color-neutral-content);
+ --menu-active-bg: var(--color-neutral);
+ font-size: 0.875rem;
+ :where(li ul) {
+ position: relative;
+ margin-inline-start: calc(0.25rem * 4);
+ padding-inline-start: calc(0.25rem * 2);
+ white-space: nowrap;
+ &:before {
+ position: absolute;
+ inset-inline-start: calc(0.25rem * 0);
+ top: calc(0.25rem * 3);
+ bottom: calc(0.25rem * 3);
+ background-color: var(--color-base-content);
+ opacity: 10%;
+ width: var(--border);
+ content: "";
+ }
+ }
+ :where(li > .menu-dropdown:not(.menu-dropdown-show)) {
+ display: none;
+ }
+ :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
+ display: grid;
+ grid-auto-flow: column;
+ align-content: flex-start;
+ align-items: center;
+ gap: calc(0.25rem * 2);
+ border-radius: var(--radius-field);
+ padding-inline: calc(0.25rem * 3);
+ padding-block: calc(0.25rem * 1.5);
+ text-align: start;
+ transition-property: color, background-color, box-shadow;
+ transition-duration: 0.2s;
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ grid-auto-columns: minmax(auto, max-content) auto max-content;
+ text-wrap: balance;
+ user-select: none;
+ }
+ :where(li > details > summary) {
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ &::-webkit-details-marker {
+ display: none;
+ }
+ }
+ :where(li > details > summary), :where(li > .menu-dropdown-toggle) {
+ &:after {
+ justify-self: flex-end;
+ display: block;
+ height: 0.375rem;
+ width: 0.375rem;
+ rotate: -135deg;
+ translate: 0 -1px;
+ transition-property: rotate, translate;
+ transition-duration: 0.2s;
+ content: "";
+ transform-origin: 50% 50%;
+ box-shadow: 2px 2px inset;
+ pointer-events: none;
+ }
+ }
+ :where(li > details[open] > summary):after, :where(li > .menu-dropdown-toggle.menu-dropdown-show):after {
+ rotate: 45deg;
+ translate: 0 1px;
+ }
+ :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title), li:not(.menu-title, .disabled) > details > summary:not(.menu-title) ):not(.menu-active, :active, .btn) {
+ &.menu-focus, &:focus-visible {
+ cursor: pointer;
+ background-color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);
+ }
+ color: var(--color-base-content);
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ }
+ }
+ :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title):not(.menu-active, :active, .btn):hover, li:not(.menu-title, .disabled) > details > summary:not(.menu-title):not(.menu-active, :active, .btn):hover ) {
+ cursor: pointer;
+ background-color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);
+ }
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ box-shadow: 0 1px oklch(0% 0 0 / 0.01) inset, 0 -1px oklch(100% 0 0 / 0.01) inset;
+ }
+ :where(li:empty) {
+ background-color: var(--color-base-content);
+ opacity: 10%;
+ margin: 0.5rem 1rem;
+ height: 1px;
+ }
+ :where(li) {
+ position: relative;
+ display: flex;
+ flex-shrink: 0;
+ flex-direction: column;
+ flex-wrap: wrap;
+ align-items: stretch;
+ .badge {
+ justify-self: flex-end;
+ }
+ & > *:not(ul, .menu-title, details, .btn):active, & > *:not(ul, .menu-title, details, .btn).menu-active, & > details > summary:active {
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ color: var(--menu-active-fg);
+ background-color: var(--menu-active-bg);
+ background-size: auto, calc(var(--noise) * 100%);
+ background-image: none, var(--fx-noise);
+ &:not(&:active) {
+ box-shadow: 0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg);
+ }
+ }
+ &.menu-disabled {
+ pointer-events: none;
+ color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, var(--color-base-content) 20%, transparent);
+ }
+ }
+ }
+ .dropdown:focus-within {
+ .menu-dropdown-toggle:after {
+ rotate: 45deg;
+ translate: 0 1px;
+ }
+ }
+ .dropdown-content {
+ margin-top: calc(0.25rem * 2);
+ padding: calc(0.25rem * 2);
+ &:before {
+ display: none;
+ }
+ }
+ }
+ .dropdown {
+ position: relative;
+ display: inline-block;
+ position-area: var(--anchor-v, bottom) var(--anchor-h, span-right);
+ & > *:not(summary):focus {
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ }
+ .dropdown-content {
+ position: absolute;
+ }
+ &:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within) {
+ .dropdown-content {
+ display: none;
+ transform-origin: top;
+ opacity: 0%;
+ scale: 95%;
+ }
+ }
+ &[popover], .dropdown-content {
+ z-index: 999;
+ animation: dropdown 0.2s;
+ transition-property: opacity, scale, display;
+ transition-behavior: allow-discrete;
+ transition-duration: 0.2s;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ }
+ @starting-style {
+ &[popover], .dropdown-content {
+ scale: 95%;
+ opacity: 0;
+ }
+ }
+ &.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within {
+ > [tabindex]:first-child {
+ pointer-events: none;
+ }
+ .dropdown-content {
+ opacity: 100%;
+ }
+ }
+ &.dropdown-hover:hover {
+ .dropdown-content {
+ opacity: 100%;
+ scale: 100%;
+ }
+ }
+ &:is(details) {
+ summary {
+ &::-webkit-details-marker {
+ display: none;
+ }
+ }
+ }
+ &.dropdown-open, &:focus, &:focus-within {
+ .dropdown-content {
+ scale: 100%;
+ }
+ }
+ &:where([popover]) {
+ background: #0000;
+ }
+ &[popover] {
+ position: fixed;
+ color: inherit;
+ @supports not (position-area: bottom) {
+ margin: auto;
+ &.dropdown-open:not(:popover-open) {
+ display: none;
+ transform-origin: top;
+ opacity: 0%;
+ scale: 95%;
+ }
+ &::backdrop {
+ background-color: color-mix(in oklab, #000 30%, #0000);
+ }
+ }
+ &:not(.dropdown-open, :popover-open) {
+ display: none;
+ transform-origin: top;
+ opacity: 0%;
+ scale: 95%;
+ }
+ }
+ }
+ .btn {
+ :where(&) {
+ width: unset;
+ }
+ display: inline-flex;
+ flex-shrink: 0;
+ cursor: pointer;
+ flex-wrap: nowrap;
+ align-items: center;
+ justify-content: center;
+ gap: calc(0.25rem * 1.5);
+ text-align: center;
+ vertical-align: middle;
+ outline-offset: 2px;
+ webkit-user-select: none;
+ user-select: none;
+ padding-inline: var(--btn-p);
+ color: var(--btn-fg);
+ --tw-prose-links: var(--btn-fg);
+ height: var(--size);
+ font-size: var(--fontsize, 0.875rem);
+ font-weight: 600;
+ outline-color: var(--btn-color, var(--color-base-content));
+ transition-property: color, background-color, border-color, box-shadow;
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transition-duration: 0.2s;
+ border-start-start-radius: var(--join-ss, var(--radius-field));
+ border-start-end-radius: var(--join-se, var(--radius-field));
+ border-end-start-radius: var(--join-es, var(--radius-field));
+ border-end-end-radius: var(--join-ee, var(--radius-field));
+ background-color: var(--btn-bg);
+ background-size: auto, calc(var(--noise) * 100%);
+ background-image: none, var(--btn-noise);
+ border-width: var(--border);
+ border-style: solid;
+ border-color: var(--btn-border);
+ text-shadow: 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15));
+ touch-action: manipulation;
+ box-shadow: 0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow);
+ --size: calc(var(--size-field, 0.25rem) * 10);
+ --btn-bg: var(--btn-color, var(--color-base-200));
+ --btn-fg: var(--color-base-content);
+ --btn-p: 1rem;
+ --btn-border: var(--btn-bg);
+ @supports (color: color-mix(in lab, red, red)) {
+ --btn-border: color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%));
+ }
+ --btn-shadow: 0 3px 2px -2px var(--btn-bg),
+ 0 4px 3px -2px var(--btn-bg);
+ @supports (color: color-mix(in lab, red, red)) {
+ --btn-shadow: 0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000),
+ 0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000);
+ }
+ --btn-noise: var(--fx-noise);
+ .prose & {
+ text-decoration-line: none;
+ }
+ @media (hover: hover) {
+ &:hover {
+ --btn-bg: var(--btn-color, var(--color-base-200));
+ @supports (color: color-mix(in lab, red, red)) {
+ --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%);
+ }
+ }
+ }
+ &:focus-visible {
+ outline-width: 2px;
+ outline-style: solid;
+ isolation: isolate;
+ }
+ &:active:not(.btn-active) {
+ translate: 0 0.5px;
+ --btn-bg: var(--btn-color, var(--color-base-200));
+ @supports (color: color-mix(in lab, red, red)) {
+ --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%);
+ }
+ --btn-border: var(--btn-color, var(--color-base-200));
+ @supports (color: color-mix(in lab, red, red)) {
+ --btn-border: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%);
+ }
+ --btn-shadow: 0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0);
+ }
+ &:is(:disabled, [disabled], .btn-disabled) {
+ &:not(.btn-link, .btn-ghost) {
+ background-color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);
+ }
+ box-shadow: none;
+ }
+ pointer-events: none;
+ --btn-border: #0000;
+ --btn-noise: none;
+ --btn-fg: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --btn-fg: color-mix(in oklch, var(--color-base-content) 20%, #0000);
+ }
+ @media (hover: hover) {
+ &:hover {
+ pointer-events: none;
+ background-color: var(--color-neutral);
+ @supports (color: color-mix(in lab, red, red)) {
+ background-color: color-mix(in oklab, var(--color-neutral) 20%, transparent);
+ }
+ --btn-border: #0000;
+ --btn-fg: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --btn-fg: color-mix(in oklch, var(--color-base-content) 20%, #0000);
+ }
+ }
+ }
+ }
+ &:is(input[type="checkbox"], input[type="radio"]) {
+ appearance: none;
+ &::after {
+ content: attr(aria-label);
+ }
+ }
+ &:where(input:checked:not(.filter .btn)) {
+ --btn-color: var(--color-primary);
+ --btn-fg: var(--color-primary-content);
+ isolation: isolate;
+ }
+ }
+ .list {
+ display: flex;
+ flex-direction: column;
+ font-size: 0.875rem;
+ :where(.list-row) {
+ --list-grid-cols: minmax(0, auto) 1fr;
+ position: relative;
+ display: grid;
+ grid-auto-flow: column;
+ gap: calc(0.25rem * 4);
+ border-radius: var(--radius-box);
+ padding: calc(0.25rem * 4);
+ word-break: break-word;
+ grid-template-columns: var(--list-grid-cols);
+ &:has(.list-col-grow:nth-child(1)) {
+ --list-grid-cols: 1fr;
+ }
+ &:has(.list-col-grow:nth-child(2)) {
+ --list-grid-cols: minmax(0, auto) 1fr;
+ }
+ &:has(.list-col-grow:nth-child(3)) {
+ --list-grid-cols: minmax(0, auto) minmax(0, auto) 1fr;
+ }
+ &:has(.list-col-grow:nth-child(4)) {
+ --list-grid-cols: minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr;
+ }
+ &:has(.list-col-grow:nth-child(5)) {
+ --list-grid-cols: minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr;
+ }
+ &:has(.list-col-grow:nth-child(6)) {
+ --list-grid-cols: minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto)
+ minmax(0, auto) 1fr;
+ }
+ :not(.list-col-wrap) {
+ grid-row-start: 1;
+ }
+ }
+ & > :not(:last-child) {
+ &.list-row, .list-row {
+ &:after {
+ content: "";
+ border-bottom: var(--border) solid;
+ inset-inline: var(--radius-box);
+ position: absolute;
+ bottom: calc(0.25rem * 0);
+ border-color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ border-color: color-mix(in oklab, var(--color-base-content) 5%, transparent);
+ }
+ }
+ }
+ }
+ }
+ .toast {
+ position: fixed;
+ inset-inline-start: auto;
+ inset-inline-end: calc(0.25rem * 4);
+ top: auto;
+ bottom: calc(0.25rem * 4);
+ display: flex;
+ flex-direction: column;
+ gap: calc(0.25rem * 2);
+ background-color: transparent;
+ translate: var(--toast-x, 0) var(--toast-y, 0);
+ width: max-content;
+ max-width: calc(100vw - 2rem);
+ & > * {
+ animation: toast 0.25s ease-out;
+ }
+ &:where(.toast-start) {
+ inset-inline-start: calc(0.25rem * 4);
+ inset-inline-end: auto;
+ --toast-x: 0;
+ }
+ &:where(.toast-center) {
+ inset-inline-start: calc(1/2 * 100%);
+ inset-inline-end: calc(1/2 * 100%);
+ --toast-x: -50%;
+ }
+ &:where(.toast-end) {
+ inset-inline-start: auto;
+ inset-inline-end: calc(0.25rem * 4);
+ --toast-x: 0;
+ }
+ &:where(.toast-bottom) {
+ top: auto;
+ bottom: calc(0.25rem * 4);
+ --toast-y: 0;
+ }
+ &:where(.toast-middle) {
+ top: calc(1/2 * 100%);
+ bottom: auto;
+ --toast-y: -50%;
+ }
+ &:where(.toast-top) {
+ top: calc(0.25rem * 4);
+ bottom: auto;
+ --toast-y: 0;
+ }
+ }
+ .toggle {
+ border: var(--border) solid currentColor;
+ color: var(--input-color);
+ position: relative;
+ display: inline-grid;
+ flex-shrink: 0;
+ cursor: pointer;
+ appearance: none;
+ place-content: center;
+ vertical-align: middle;
+ webkit-user-select: none;
+ user-select: none;
+ grid-template-columns: 0fr 1fr 1fr;
+ --radius-selector-max: calc(
+ var(--radius-selector) + var(--radius-selector) + var(--radius-selector)
+ );
+ border-radius: calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) );
+ padding: var(--toggle-p);
+ box-shadow: 0 1px currentColor inset;
+ @supports (color: color-mix(in lab, red, red)) {
+ box-shadow: 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset;
+ }
+ transition: color 0.3s, grid-template-columns 0.2s;
+ --input-color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --input-color: color-mix(in oklab, var(--color-base-content) 50%, #0000);
+ }
+ --toggle-p: calc(var(--size) * 0.125);
+ --size: calc(var(--size-selector, 0.25rem) * 6);
+ width: calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2);
+ height: var(--size);
+ > * {
+ z-index: 1;
+ grid-column: span 1 / span 1;
+ grid-column-start: 2;
+ grid-row-start: 1;
+ height: 100%;
+ cursor: pointer;
+ appearance: none;
+ background-color: transparent;
+ padding: calc(0.25rem * 0.5);
+ transition: opacity 0.2s, rotate 0.4s;
+ border: none;
+ &:focus {
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ }
+ &:nth-child(2) {
+ color: var(--color-base-100);
+ rotate: 0deg;
+ }
+ &:nth-child(3) {
+ color: var(--color-base-100);
+ opacity: 0%;
+ rotate: -15deg;
+ }
+ }
+ &:has(:checked) {
+ > :nth-child(2) {
+ opacity: 0%;
+ rotate: 15deg;
+ }
+ > :nth-child(3) {
+ opacity: 100%;
+ rotate: 0deg;
+ }
+ }
+ &:before {
+ position: relative;
+ inset-inline-start: calc(0.25rem * 0);
+ grid-column-start: 2;
+ grid-row-start: 1;
+ aspect-ratio: 1 / 1;
+ height: 100%;
+ border-radius: var(--radius-selector);
+ background-color: currentColor;
+ translate: 0;
+ --tw-content: "";
+ content: var(--tw-content);
+ transition: background-color 0.1s, translate 0.2s, inset-inline-start 0.2s;
+ box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px currentColor;
+ @supports (color: color-mix(in lab, red, red)) {
+ box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000);
+ }
+ background-size: auto, calc(var(--noise) * 100%);
+ background-image: none, var(--fx-noise);
+ }
+ @media (forced-colors: active) {
+ &:before {
+ outline-style: var(--tw-outline-style);
+ outline-width: 1px;
+ outline-offset: calc(1px * -1);
+ }
+ }
+ @media print {
+ &:before {
+ outline: 0.25rem solid;
+ outline-offset: -1rem;
+ }
+ }
+ &:focus-visible, &:has(:focus-visible) {
+ outline: 2px solid currentColor;
+ outline-offset: 2px;
+ }
+ &:checked, &[aria-checked="true"], &:has(> input:checked) {
+ grid-template-columns: 1fr 1fr 0fr;
+ background-color: var(--color-base-100);
+ --input-color: var(--color-base-content);
+ &:before {
+ background-color: currentColor;
+ }
+ @starting-style {
+ &:before {
+ opacity: 0;
+ }
+ }
+ }
+ &:indeterminate {
+ grid-template-columns: 0.5fr 1fr 0.5fr;
+ }
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 30%;
+ &:before {
+ background-color: transparent;
+ border: var(--border) solid currentColor;
+ }
+ }
+ }
+ .input {
+ cursor: text;
+ border: var(--border) solid #0000;
+ position: relative;
+ display: inline-flex;
+ flex-shrink: 1;
+ appearance: none;
+ align-items: center;
+ gap: calc(0.25rem * 2);
+ background-color: var(--color-base-100);
+ padding-inline: calc(0.25rem * 3);
+ vertical-align: middle;
+ white-space: nowrap;
+ width: clamp(3rem, 20rem, 100%);
+ height: var(--size);
+ font-size: 0.875rem;
+ touch-action: manipulation;
+ border-start-start-radius: var(--join-ss, var(--radius-field));
+ border-start-end-radius: var(--join-se, var(--radius-field));
+ border-end-start-radius: var(--join-es, var(--radius-field));
+ border-end-end-radius: var(--join-ee, var(--radius-field));
+ border-color: var(--input-color);
+ box-shadow: 0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;
+ @supports (color: color-mix(in lab, red, red)) {
+ box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;
+ }
+ --size: calc(var(--size-field, 0.25rem) * 10);
+ --input-color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);
+ }
+ &:where(input) {
+ display: inline-flex;
+ }
+ :where(input) {
+ display: inline-flex;
+ height: 100%;
+ width: 100%;
+ appearance: none;
+ background-color: transparent;
+ border: none;
+ &:focus, &:focus-within {
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ }
+ }
+ :where(input[type="url"]), :where(input[type="email"]) {
+ direction: ltr;
+ }
+ :where(input[type="date"]) {
+ display: inline-block;
+ }
+ &:focus, &:focus-within {
+ --input-color: var(--color-base-content);
+ box-shadow: 0 1px var(--input-color);
+ @supports (color: color-mix(in lab, red, red)) {
+ box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000);
+ }
+ outline: 2px solid var(--input-color);
+ outline-offset: 2px;
+ isolation: isolate;
+ z-index: 1;
+ }
+ &:has(> input[disabled]), &:is(:disabled, [disabled]) {
+ cursor: not-allowed;
+ border-color: var(--color-base-200);
+ background-color: var(--color-base-200);
+ color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, var(--color-base-content) 40%, transparent);
+ }
+ &::placeholder {
+ color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, var(--color-base-content) 20%, transparent);
+ }
+ }
+ box-shadow: none;
+ }
+ &:has(> input[disabled]) > input[disabled] {
+ cursor: not-allowed;
+ }
+ &::-webkit-date-and-time-value {
+ text-align: inherit;
+ }
+ &[type="number"] {
+ &::-webkit-inner-spin-button {
+ margin-block: calc(0.25rem * -3);
+ margin-inline-end: calc(0.25rem * -3);
+ }
+ }
+ &::-webkit-calendar-picker-indicator {
+ position: absolute;
+ inset-inline-end: 0.75em;
+ }
+ }
+ .table {
+ font-size: 0.875rem;
+ position: relative;
+ width: 100%;
+ border-radius: var(--radius-box);
+ text-align: left;
+ &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
+ text-align: right;
+ }
+ tr.row-hover {
+ &, &:nth-child(even) {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-base-200);
+ }
+ }
+ }
+ }
+ :where(th, td) {
+ padding-inline: calc(0.25rem * 4);
+ padding-block: calc(0.25rem * 3);
+ vertical-align: middle;
+ }
+ :where(thead, tfoot) {
+ white-space: nowrap;
+ color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
+ }
+ font-size: 0.875rem;
+ font-weight: 600;
+ }
+ :where(tfoot) {
+ border-top: var(--border) solid var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ border-top: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000);
+ }
+ }
+ :where(.table-pin-rows thead tr) {
+ position: sticky;
+ top: calc(0.25rem * 0);
+ z-index: 1;
+ background-color: var(--color-base-100);
+ }
+ :where(.table-pin-rows tfoot tr) {
+ position: sticky;
+ bottom: calc(0.25rem * 0);
+ z-index: 1;
+ background-color: var(--color-base-100);
+ }
+ :where(.table-pin-cols tr th) {
+ position: sticky;
+ right: calc(0.25rem * 0);
+ left: calc(0.25rem * 0);
+ background-color: var(--color-base-100);
+ }
+ :where(thead tr, tbody tr:not(:last-child)) {
+ border-bottom: var(--border) solid var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ border-bottom: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000);
+ }
+ }
+ }
+ .chat-bubble {
+ position: relative;
+ display: block;
+ width: fit-content;
+ border-radius: var(--radius-field);
+ background-color: var(--color-base-300);
+ padding-inline: calc(0.25rem * 4);
+ padding-block: calc(0.25rem * 2);
+ color: var(--color-base-content);
+ grid-row-end: 3;
+ min-height: 2rem;
+ min-width: 2.5rem;
+ max-width: 90%;
+ &:before {
+ position: absolute;
+ bottom: calc(0.25rem * 0);
+ height: calc(0.25rem * 3);
+ width: calc(0.25rem * 3);
+ background-color: inherit;
+ content: "";
+ mask-repeat: no-repeat;
+ mask-image: var(--mask-chat);
+ mask-position: 0px -1px;
+ mask-size: 13px;
+ }
+ }
+ .checkbox {
+ border: var(--border) solid var(--input-color, var(--color-base-content));
+ @supports (color: color-mix(in lab, red, red)) {
+ border: var(--border) solid var(--input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000));
+ }
+ position: relative;
+ display: inline-block;
+ flex-shrink: 0;
+ cursor: pointer;
+ appearance: none;
+ border-radius: var(--radius-selector);
+ padding: calc(0.25rem * 1);
+ vertical-align: middle;
+ color: var(--color-base-content);
+ box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0 #0000 inset, 0 0 #0000;
+ transition: background-color 0.2s, box-shadow 0.2s;
+ --size: calc(var(--size-selector, 0.25rem) * 6);
+ width: var(--size);
+ height: var(--size);
+ background-size: auto, calc(var(--noise) * 100%);
+ background-image: none, var(--fx-noise);
+ &:before {
+ --tw-content: "";
+ content: var(--tw-content);
+ display: block;
+ width: 100%;
+ height: 100%;
+ rotate: 45deg;
+ background-color: currentColor;
+ opacity: 0%;
+ transition: clip-path 0.3s, opacity 0.1s, rotate 0.3s, translate 0.3s;
+ transition-delay: 0.1s;
+ clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%);
+ box-shadow: 0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;
+ font-size: 1rem;
+ line-height: 0.75;
+ }
+ &:focus-visible {
+ outline: 2px solid var(--input-color, currentColor);
+ outline-offset: 2px;
+ }
+ &:checked, &[aria-checked="true"] {
+ background-color: var(--input-color, #0000);
+ box-shadow: 0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1));
+ &:before {
+ clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%);
+ opacity: 100%;
+ }
+ @media (forced-colors: active) {
+ &:before {
+ rotate: 0deg;
+ background-color: transparent;
+ --tw-content: "✔︎";
+ clip-path: none;
+ }
+ }
+ @media print {
+ &:before {
+ rotate: 0deg;
+ background-color: transparent;
+ --tw-content: "✔︎";
+ clip-path: none;
+ }
+ }
+ }
+ &:indeterminate {
+ &:before {
+ rotate: 0deg;
+ opacity: 100%;
+ translate: 0 -35%;
+ clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%);
+ }
+ }
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 20%;
+ }
+ }
+ .radio {
+ position: relative;
+ display: inline-block;
+ flex-shrink: 0;
+ cursor: pointer;
+ appearance: none;
+ border-radius: calc(infinity * 1px);
+ padding: calc(0.25rem * 1);
+ vertical-align: middle;
+ border: var(--border) solid var(--input-color, currentColor);
+ @supports (color: color-mix(in lab, red, red)) {
+ border: var(--border) solid var(--input-color, color-mix(in srgb, currentColor 20%, #0000));
+ }
+ box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset;
+ --size: calc(var(--size-selector, 0.25rem) * 6);
+ width: var(--size);
+ height: var(--size);
+ color: var(--input-color, currentColor);
+ &:before {
+ display: block;
+ width: 100%;
+ height: 100%;
+ border-radius: calc(infinity * 1px);
+ --tw-content: "";
+ content: var(--tw-content);
+ background-size: auto, calc(var(--noise) * 100%);
+ background-image: none, var(--fx-noise);
+ }
+ &:focus-visible {
+ outline: 2px solid currentColor;
+ }
+ &:checked, &[aria-checked="true"] {
+ animation: radio 0.2s ease-out;
+ border-color: currentColor;
+ background-color: var(--color-base-100);
+ &:before {
+ background-color: currentColor;
+ box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1));
+ }
+ @media (forced-colors: active) {
+ &:before {
+ outline-style: var(--tw-outline-style);
+ outline-width: 1px;
+ outline-offset: calc(1px * -1);
+ }
+ }
+ @media print {
+ &:before {
+ outline: 0.25rem solid;
+ outline-offset: -1rem;
+ }
+ }
+ }
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 20%;
+ }
+ }
+ .rating {
+ position: relative;
+ display: inline-flex;
+ vertical-align: middle;
+ & input {
+ border: none;
+ appearance: none;
+ }
+ :where(*) {
+ animation: rating 0.25s ease-out;
+ height: calc(0.25rem * 6);
+ width: calc(0.25rem * 6);
+ border-radius: 0;
+ background-color: var(--color-base-content);
+ opacity: 20%;
+ &:is(input) {
+ cursor: pointer;
+ }
+ }
+ & .rating-hidden {
+ width: calc(0.25rem * 2);
+ background-color: transparent;
+ }
+ input[type="radio"]:checked {
+ background-image: none;
+ }
+ * {
+ &:checked, &[aria-checked="true"], &[aria-current="true"], &:has(~ *:checked, ~ *[aria-checked="true"], ~ *[aria-current="true"]) {
+ opacity: 100%;
+ }
+ &:focus-visible {
+ transition: scale 0.2s ease-out;
+ scale: 1.1;
+ }
+ }
+ & *:active:focus {
+ animation: none;
+ scale: 1.1;
+ }
+ &.rating-xs :where(*:not(.rating-hidden)) {
+ width: calc(0.25rem * 4);
+ height: calc(0.25rem * 4);
+ }
+ &.rating-sm :where(*:not(.rating-hidden)) {
+ width: calc(0.25rem * 5);
+ height: calc(0.25rem * 5);
+ }
+ &.rating-md :where(*:not(.rating-hidden)) {
+ width: calc(0.25rem * 6);
+ height: calc(0.25rem * 6);
+ }
+ &.rating-lg :where(*:not(.rating-hidden)) {
+ width: calc(0.25rem * 7);
+ height: calc(0.25rem * 7);
+ }
+ &.rating-xl :where(*:not(.rating-hidden)) {
+ width: calc(0.25rem * 8);
+ height: calc(0.25rem * 8);
+ }
+ }
+ .progress {
+ position: relative;
+ height: calc(0.25rem * 2);
+ width: 100%;
+ appearance: none;
+ overflow: hidden;
+ border-radius: var(--radius-box);
+ background-color: currentColor;
+ @supports (color: color-mix(in lab, red, red)) {
+ background-color: color-mix(in oklab, currentColor 20%, transparent);
+ }
+ color: var(--color-base-content);
+ &:indeterminate {
+ background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% );
+ background-size: 200%;
+ background-position-x: 15%;
+ animation: progress 5s ease-in-out infinite;
+ @supports (-moz-appearance: none) {
+ &::-moz-progress-bar {
+ background-color: transparent;
+ background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% );
+ background-size: 200%;
+ background-position-x: 15%;
+ animation: progress 5s ease-in-out infinite;
+ }
+ }
+ }
+ @supports (-moz-appearance: none) {
+ &::-moz-progress-bar {
+ border-radius: var(--radius-box);
+ background-color: currentColor;
+ }
+ }
+ @supports (-webkit-appearance: none) {
+ &::-webkit-progress-bar {
+ border-radius: var(--radius-box);
+ background-color: transparent;
+ }
+ &::-webkit-progress-value {
+ border-radius: var(--radius-box);
+ background-color: currentColor;
+ }
+ }
+ }
+ .absolute {
+ position: absolute;
+ }
+ .fixed {
+ position: fixed;
+ }
+ .relative {
+ position: relative;
+ }
+ .dropdown-right {
+ --anchor-h: right;
+ --anchor-v: span-bottom;
+ .dropdown-content {
+ inset-inline-start: 100%;
+ top: calc(0.25rem * 0);
+ bottom: auto;
+ transform-origin: left;
+ }
+ }
+ .chat-start {
+ place-items: start;
+ grid-template-columns: auto 1fr;
+ .chat-header {
+ grid-column-start: 2;
+ }
+ .chat-footer {
+ grid-column-start: 2;
+ }
+ .chat-image {
+ grid-column-start: 1;
+ }
+ .chat-bubble {
+ grid-column-start: 2;
+ border-end-start-radius: 0;
+ &:before {
+ transform: rotateY(0deg);
+ inset-inline-start: -0.75rem;
+ }
+ [dir="rtl"] &:before {
+ transform: rotateY(180deg);
+ }
+ }
+ }
+ .dropdown-left {
+ --anchor-h: left;
+ --anchor-v: span-bottom;
+ .dropdown-content {
+ inset-inline-end: 100%;
+ top: calc(0.25rem * 0);
+ bottom: auto;
+ transform-origin: right;
+ }
+ }
+ .dropdown-center {
+ --anchor-h: center;
+ :where(.dropdown-content) {
+ inset-inline-end: calc(1/2 * 100%);
+ translate: 50% 0;
+ [dir="rtl"] & {
+ translate: -50% 0;
+ }
+ }
+ &.dropdown-left {
+ --anchor-h: left;
+ --anchor-v: center;
+ .dropdown-content {
+ top: auto;
+ bottom: calc(1/2 * 100%);
+ translate: 0 50%;
+ }
+ }
+ &.dropdown-right {
+ --anchor-h: right;
+ --anchor-v: center;
+ .dropdown-content {
+ top: auto;
+ bottom: calc(1/2 * 100%);
+ translate: 0 50%;
+ }
+ }
+ }
+ .dropdown-end {
+ --anchor-h: span-left;
+ :where(.dropdown-content) {
+ inset-inline-end: calc(0.25rem * 0);
+ translate: 0 0;
+ [dir="rtl"] & {
+ translate: 0 0;
+ }
+ }
+ &.dropdown-left {
+ --anchor-h: left;
+ --anchor-v: span-top;
+ .dropdown-content {
+ top: auto;
+ bottom: calc(0.25rem * 0);
+ }
+ }
+ &.dropdown-right {
+ --anchor-h: right;
+ --anchor-v: span-top;
+ .dropdown-content {
+ top: auto;
+ bottom: calc(0.25rem * 0);
+ }
+ }
+ }
+ .right-8 {
+ right: calc(var(--spacing) * 8);
+ }
+ .bottom-8 {
+ bottom: calc(var(--spacing) * 8);
+ }
+ .textarea {
+ border: var(--border) solid #0000;
+ min-height: calc(0.25rem * 20);
+ flex-shrink: 1;
+ appearance: none;
+ border-radius: var(--radius-field);
+ background-color: var(--color-base-100);
+ padding-block: calc(0.25rem * 2);
+ vertical-align: middle;
+ width: clamp(3rem, 20rem, 100%);
+ padding-inline-start: 0.75rem;
+ padding-inline-end: 0.75rem;
+ font-size: 0.875rem;
+ touch-action: manipulation;
+ border-color: var(--input-color);
+ box-shadow: 0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;
+ @supports (color: color-mix(in lab, red, red)) {
+ box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;
+ }
+ --input-color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);
+ }
+ textarea {
+ appearance: none;
+ background-color: transparent;
+ border: none;
+ &:focus, &:focus-within {
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ }
+ }
+ &:focus, &:focus-within {
+ --input-color: var(--color-base-content);
+ box-shadow: 0 1px var(--input-color);
+ @supports (color: color-mix(in lab, red, red)) {
+ box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000);
+ }
+ outline: 2px solid var(--input-color);
+ outline-offset: 2px;
+ isolation: isolate;
+ }
+ &:has(> textarea[disabled]), &:is(:disabled, [disabled]) {
+ cursor: not-allowed;
+ border-color: var(--color-base-200);
+ background-color: var(--color-base-200);
+ color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, var(--color-base-content) 40%, transparent);
+ }
+ &::placeholder {
+ color: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, var(--color-base-content) 20%, transparent);
+ }
+ }
+ box-shadow: none;
+ }
+ &:has(> textarea[disabled]) > textarea[disabled] {
+ cursor: not-allowed;
+ }
+ }
+ .z-1 {
+ z-index: 1;
+ }
+ .drawer-content {
+ grid-column-start: 2;
+ grid-row-start: 1;
+ min-width: calc(0.25rem * 0);
+ }
+ .chat-image {
+ grid-row: span 2 / span 2;
+ align-self: flex-end;
+ }
+ .chat-footer {
+ grid-row-start: 3;
+ display: flex;
+ gap: calc(0.25rem * 1);
+ font-size: 0.6875rem;
+ }
+ .chat-header {
+ grid-row-start: 1;
+ display: flex;
+ gap: calc(0.25rem * 1);
+ font-size: 0.6875rem;
+ }
+ .m-1 {
+ margin: calc(var(--spacing) * 1);
+ }
+ .filter {
+ display: flex;
+ flex-wrap: wrap;
+ input[type="radio"] {
+ width: auto;
+ }
+ input {
+ overflow: hidden;
+ opacity: 100%;
+ scale: 1;
+ transition: margin 0.1s, opacity 0.3s, padding 0.3s, border-width 0.1s;
+ &:not(:last-child) {
+ margin-inline-end: calc(0.25rem * 1);
+ }
+ &.filter-reset {
+ aspect-ratio: 1 / 1;
+ &::after {
+ content: "×";
+ }
+ }
+ }
+ &:not(:has(input:checked:not(.filter-reset))) {
+ .filter-reset, input[type="reset"] {
+ scale: 0;
+ border-width: 0;
+ margin-inline: calc(0.25rem * 0);
+ width: calc(0.25rem * 0);
+ padding-inline: calc(0.25rem * 0);
+ opacity: 0%;
+ }
+ }
+ &:has(input:checked:not(.filter-reset)) {
+ input:not(:checked, .filter-reset, input[type="reset"]) {
+ scale: 0;
+ border-width: 0;
+ margin-inline: calc(0.25rem * 0);
+ width: calc(0.25rem * 0);
+ padding-inline: calc(0.25rem * 0);
+ opacity: 0%;
+ }
+ }
+ }
+ .mt-2 {
+ margin-top: calc(var(--spacing) * 2);
+ }
+ .mt-4 {
+ margin-top: calc(var(--spacing) * 4);
+ }
+ .mt-8 {
+ margin-top: calc(var(--spacing) * 8);
+ }
+ .mt-16 {
+ margin-top: calc(var(--spacing) * 16);
+ }
+ .mb-4 {
+ margin-bottom: calc(var(--spacing) * 4);
+ }
+ .mb-8 {
+ margin-bottom: calc(var(--spacing) * 8);
+ }
+ .ml-2 {
+ margin-left: calc(var(--spacing) * 2);
+ }
+ .ml-4 {
+ margin-left: calc(var(--spacing) * 4);
+ }
+ .ml-auto {
+ margin-left: auto;
+ }
+ .badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: calc(0.25rem * 2);
+ border-radius: var(--radius-selector);
+ vertical-align: middle;
+ color: var(--badge-fg);
+ border: var(--border) solid var(--badge-color, var(--color-base-200));
+ font-size: 0.875rem;
+ width: fit-content;
+ padding-inline: calc(0.25rem * 3 - var(--border));
+ background-size: auto, calc(var(--noise) * 100%);
+ background-image: none, var(--fx-noise);
+ background-color: var(--badge-bg);
+ --badge-bg: var(--badge-color, var(--color-base-100));
+ --badge-fg: var(--color-base-content);
+ --size: calc(var(--size-selector, 0.25rem) * 6);
+ height: var(--size);
+ }
+ .alert {
+ display: grid;
+ align-items: center;
+ gap: calc(0.25rem * 4);
+ border-radius: var(--radius-box);
+ padding-inline: calc(0.25rem * 4);
+ padding-block: calc(0.25rem * 3);
+ color: var(--color-base-content);
+ background-color: var(--alert-color, var(--color-base-200));
+ justify-content: start;
+ justify-items: start;
+ grid-auto-flow: column;
+ grid-template-columns: auto;
+ text-align: start;
+ border: var(--border) solid var(--color-base-200);
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ background-size: auto, calc(var(--noise) * 100%);
+ background-image: none, var(--fx-noise);
+ box-shadow: 0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px #000, 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08));
+ @supports (color: color-mix(in lab, red, red)) {
+ box-shadow: 0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px color-mix( in oklab, color-mix(in oklab, #000 20%, var(--alert-color, var(--color-base-200))) calc(var(--depth) * 20%), #0000 ), 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08));
+ }
+ &:has(:nth-child(2)) {
+ grid-template-columns: auto minmax(auto, 1fr);
+ }
+ &.alert-outline {
+ background-color: transparent;
+ color: var(--alert-color);
+ box-shadow: none;
+ background-image: none;
+ }
+ &.alert-dash {
+ background-color: transparent;
+ color: var(--alert-color);
+ border-style: dashed;
+ box-shadow: none;
+ background-image: none;
+ }
+ &.alert-soft {
+ color: var(--alert-color, var(--color-base-content));
+ background: var(--alert-color, var(--color-base-content));
+ @supports (color: color-mix(in lab, red, red)) {
+ background: color-mix( in oklab, var(--alert-color, var(--color-base-content)) 8%, var(--color-base-100) );
+ }
+ border-color: var(--alert-color, var(--color-base-content));
+ @supports (color: color-mix(in lab, red, red)) {
+ border-color: color-mix( in oklab, var(--alert-color, var(--color-base-content)) 10%, var(--color-base-100) );
+ }
+ box-shadow: none;
+ background-image: none;
+ }
+ }
+ .join {
+ display: inline-flex;
+ align-items: stretch;
+ --join-ss: 0;
+ --join-se: 0;
+ --join-es: 0;
+ --join-ee: 0;
+ :where(.join-item) {
+ border-start-start-radius: var(--join-ss, 0);
+ border-start-end-radius: var(--join-se, 0);
+ border-end-start-radius: var(--join-es, 0);
+ border-end-end-radius: var(--join-ee, 0);
+ * {
+ --join-ss: var(--radius-field);
+ --join-se: var(--radius-field);
+ --join-es: var(--radius-field);
+ --join-ee: var(--radius-field);
+ }
+ }
+ > .join-item:where(:first-child) {
+ --join-ss: var(--radius-field);
+ --join-se: 0;
+ --join-es: var(--radius-field);
+ --join-ee: 0;
+ }
+ :first-child:not(:last-child) {
+ :where(.join-item) {
+ --join-ss: var(--radius-field);
+ --join-se: 0;
+ --join-es: var(--radius-field);
+ --join-ee: 0;
+ }
+ }
+ > .join-item:where(:last-child) {
+ --join-ss: 0;
+ --join-se: var(--radius-field);
+ --join-es: 0;
+ --join-ee: var(--radius-field);
+ }
+ :last-child:not(:first-child) {
+ :where(.join-item) {
+ --join-ss: 0;
+ --join-se: var(--radius-field);
+ --join-es: 0;
+ --join-ee: var(--radius-field);
+ }
+ }
+ > .join-item:where(:only-child) {
+ --join-ss: var(--radius-field);
+ --join-se: var(--radius-field);
+ --join-es: var(--radius-field);
+ --join-ee: var(--radius-field);
+ }
+ :only-child {
+ :where(.join-item) {
+ --join-ss: var(--radius-field);
+ --join-se: var(--radius-field);
+ --join-es: var(--radius-field);
+ --join-ee: var(--radius-field);
+ }
+ }
+ }
+ .chat {
+ display: grid;
+ column-gap: calc(0.25rem * 3);
+ padding-block: calc(0.25rem * 1);
+ --mask-chat: url("data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e");
+ }
+ .prose {
+ :root & {
+ --tw-prose-body: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-body: color-mix(in oklab, var(--color-base-content) 80%, #0000);
+ }
+ --tw-prose-headings: var(--color-base-content);
+ --tw-prose-lead: var(--color-base-content);
+ --tw-prose-links: var(--color-base-content);
+ --tw-prose-bold: var(--color-base-content);
+ --tw-prose-counters: var(--color-base-content);
+ --tw-prose-bullets: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-bullets: color-mix(in oklab, var(--color-base-content) 50%, #0000);
+ }
+ --tw-prose-hr: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-hr: color-mix(in oklab, var(--color-base-content) 20%, #0000);
+ }
+ --tw-prose-quotes: var(--color-base-content);
+ --tw-prose-quote-borders: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-quote-borders: color-mix(in oklab, var(--color-base-content) 20%, #0000);
+ }
+ --tw-prose-captions: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-captions: color-mix(in oklab, var(--color-base-content) 50%, #0000);
+ }
+ --tw-prose-code: var(--color-base-content);
+ --tw-prose-pre-code: var(--color-neutral-content);
+ --tw-prose-pre-bg: var(--color-neutral);
+ --tw-prose-th-borders: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-th-borders: color-mix(in oklab, var(--color-base-content) 50%, #0000);
+ }
+ --tw-prose-td-borders: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-td-borders: color-mix(in oklab, var(--color-base-content) 20%, #0000);
+ }
+ --tw-prose-kbd: var(--color-base-content);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-prose-kbd: color-mix(in oklab, var(--color-base-content) 80%, #0000);
+ }
+ :where(code):not(pre > code) {
+ background-color: var(--color-base-200);
+ border-radius: var(--radius-selector);
+ border: var(--border) solid var(--color-base-300);
+ padding-inline: 0.5em;
+ font-weight: inherit;
+ &:before, &:after {
+ display: none;
+ }
+ }
+ }
+ }
+ .block {
+ display: block;
+ }
+ .flex {
+ display: flex;
+ }
+ .grid {
+ display: grid;
+ }
+ .hidden {
+ display: none;
+ }
+ .table {
+ display: table;
+ }
+ .size-12 {
+ width: calc(var(--spacing) * 12);
+ height: calc(var(--spacing) * 12);
+ }
+ .h-4 {
+ height: calc(var(--spacing) * 4);
+ }
+ .h-8 {
+ height: calc(var(--spacing) * 8);
+ }
+ .h-10 {
+ height: calc(var(--spacing) * 10);
+ }
+ .h-12 {
+ height: calc(var(--spacing) * 12);
+ }
+ .h-16 {
+ height: calc(var(--spacing) * 16);
+ }
+ .h-20 {
+ height: calc(var(--spacing) * 20);
+ }
+ .h-fit {
+ height: fit-content;
+ }
+ .h-full {
+ height: 100%;
+ }
+ .h-screen {
+ height: 100vh;
+ }
+ .max-h-12 {
+ max-height: calc(var(--spacing) * 12);
+ }
+ .max-h-screen {
+ max-height: 100vh;
+ }
+ .min-h-0 {
+ min-height: calc(var(--spacing) * 0);
+ }
+ .btn-wide {
+ width: 100%;
+ max-width: calc(0.25rem * 64);
+ }
+ .w-4 {
+ width: calc(var(--spacing) * 4);
+ }
+ .w-16 {
+ width: calc(var(--spacing) * 16);
+ }
+ .w-20 {
+ width: calc(var(--spacing) * 20);
+ }
+ .w-52 {
+ width: calc(var(--spacing) * 52);
+ }
+ .w-100 {
+ width: calc(var(--spacing) * 100);
+ }
+ .w-fit {
+ width: fit-content;
+ }
+ .w-full {
+ width: 100%;
+ }
+ .w-screen {
+ width: 100vw;
+ }
+ .max-w-20 {
+ max-width: calc(var(--spacing) * 20);
+ }
+ .max-w-\[20rem\] {
+ max-width: 20rem;
+ }
+ .min-w-110 {
+ min-width: calc(var(--spacing) * 110);
+ }
+ .min-w-xs {
+ min-width: var(--container-xs);
+ }
+ .flex-1 {
+ flex: 1;
+ }
+ .flex-shrink {
+ flex-shrink: 1;
+ }
+ .flex-shrink-0 {
+ flex-shrink: 0;
+ }
+ .border-collapse {
+ border-collapse: collapse;
+ }
+ .scale-50 {
+ --tw-scale-x: 50%;
+ --tw-scale-y: 50%;
+ --tw-scale-z: 50%;
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+ .transform {
+ transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
+ }
+ .skeleton {
+ border-radius: var(--radius-box);
+ background-color: var(--color-base-300);
+ @media (prefers-reduced-motion: reduce) {
+ transition-duration: 15s;
+ }
+ will-change: background-position;
+ animation: skeleton 1.8s ease-in-out infinite;
+ background-image: linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% );
+ background-size: 200% auto;
+ background-repeat: no-repeat;
+ background-position-x: -50%;
+ }
+ .link {
+ cursor: pointer;
+ text-decoration-line: underline;
+ &:focus {
+ --tw-outline-style: none;
+ outline-style: none;
+ @media (forced-colors: active) {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ }
+ }
+ &:focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: 2px;
+ }
+ }
+ .resize {
+ resize: both;
+ }
+ .grid-cols-4 {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+ .grid-cols-\[repeat\(auto-fit\,minmax\(5rem\,1fr\)\)\] {
+ grid-template-columns: repeat(auto-fit,minmax(5rem,1fr));
+ }
+ .flex-col {
+ flex-direction: column;
+ }
+ .flex-wrap {
+ flex-wrap: wrap;
+ }
+ .items-center {
+ align-items: center;
+ }
+ .justify-between {
+ justify-content: space-between;
+ }
+ .justify-center {
+ justify-content: center;
+ }
+ .justify-evenly {
+ justify-content: space-evenly;
+ }
+ .gap-0 {
+ gap: calc(var(--spacing) * 0);
+ }
+ .gap-2 {
+ gap: calc(var(--spacing) * 2);
+ }
+ .gap-x-2 {
+ column-gap: calc(var(--spacing) * 2);
+ }
+ .gap-x-4 {
+ column-gap: calc(var(--spacing) * 4);
+ }
+ .gap-x-8 {
+ column-gap: calc(var(--spacing) * 8);
+ }
+ .gap-y-6 {
+ row-gap: calc(var(--spacing) * 6);
+ }
+ .gap-y-8 {
+ row-gap: calc(var(--spacing) * 8);
+ }
+ .overflow-y-auto {
+ overflow-y: auto;
+ }
+ .rounded {
+ border-radius: 0.25rem;
+ }
+ .rounded-box {
+ border-radius: var(--radius-box);
+ }
+ .rounded-box {
+ border-radius: var(--radius-box);
+ }
+ .rounded-full {
+ border-radius: calc(infinity * 1px);
+ }
+ .rounded-lg {
+ border-radius: var(--radius-lg);
+ }
+ .rounded-tl-lg {
+ border-top-left-radius: var(--radius-lg);
+ }
+ .rounded-tr-lg {
+ border-top-right-radius: var(--radius-lg);
+ }
+ .rounded-br-lg {
+ border-bottom-right-radius: var(--radius-lg);
+ }
+ .rounded-bl-lg {
+ border-bottom-left-radius: var(--radius-lg);
+ }
+ .border {
+ border-style: var(--tw-border-style);
+ border-width: 1px;
+ }
+ .border-r {
+ border-right-style: var(--tw-border-style);
+ border-right-width: 1px;
+ }
+ .border-none {
+ --tw-border-style: none;
+ border-style: none;
+ }
+ .border-solid {
+ --tw-border-style: solid;
+ border-style: solid;
+ }
+ .alert-info {
+ border-color: var(--color-info);
+ color: var(--color-info-content);
+ --alert-color: var(--color-info);
+ }
+ .border-transparent {
+ border-color: transparent;
+ }
+ .border-r-base-300 {
+ border-right-color: var(--color-base-300);
+ }
+ .bg-base-100 {
+ background-color: var(--color-base-100);
+ }
+ .bg-base-200 {
+ background-color: var(--color-base-200);
+ }
+ .bg-base-300 {
+ background-color: var(--color-base-300);
+ }
+ .bg-base-content {
+ background-color: var(--color-base-content);
+ }
+ .bg-info {
+ background-color: var(--color-info);
+ }
+ .bg-transparent {
+ background-color: transparent;
+ }
+ .mask-repeat {
+ mask-repeat: repeat;
+ }
+ .fill-current {
+ fill: currentcolor;
+ }
+ .object-cover {
+ object-fit: cover;
+ }
+ .p-0 {
+ padding: calc(var(--spacing) * 0);
+ }
+ .p-2 {
+ padding: calc(var(--spacing) * 2);
+ }
+ .p-4 {
+ padding: calc(var(--spacing) * 4);
+ }
+ .px-4 {
+ padding-inline: calc(var(--spacing) * 4);
+ }
+ .pt-1 {
+ padding-top: calc(var(--spacing) * 1);
+ }
+ .pt-4 {
+ padding-top: calc(var(--spacing) * 4);
+ }
+ .pr-0 {
+ padding-right: calc(var(--spacing) * 0);
+ }
+ .pr-8 {
+ padding-right: calc(var(--spacing) * 8);
+ }
+ .pb-1 {
+ padding-bottom: calc(var(--spacing) * 1);
+ }
+ .pb-4 {
+ padding-bottom: calc(var(--spacing) * 4);
+ }
+ .pl-8 {
+ padding-left: calc(var(--spacing) * 8);
+ }
+ .text-center {
+ text-align: center;
+ }
+ .text-4xl {
+ font-size: var(--text-4xl);
+ line-height: var(--tw-leading, var(--text-4xl--line-height));
+ }
+ .text-base {
+ font-size: var(--text-base);
+ line-height: var(--tw-leading, var(--text-base--line-height));
+ }
+ .text-lg {
+ font-size: var(--text-lg);
+ line-height: var(--tw-leading, var(--text-lg--line-height));
+ }
+ .text-sm {
+ font-size: var(--text-sm);
+ line-height: var(--tw-leading, var(--text-sm--line-height));
+ }
+ .text-xl {
+ font-size: var(--text-xl);
+ line-height: var(--tw-leading, var(--text-xl--line-height));
+ }
+ .font-medium {
+ --tw-font-weight: var(--font-weight-medium);
+ font-weight: var(--font-weight-medium);
+ }
+ .font-semibold {
+ --tw-font-weight: var(--font-weight-semibold);
+ font-weight: var(--font-weight-semibold);
+ }
+ .text-wrap {
+ text-wrap: wrap;
+ }
+ .text-base-100 {
+ color: var(--color-base-100);
+ }
+ .text-base-content {
+ color: var(--color-base-content);
+ }
+ .text-info-content {
+ color: var(--color-info-content);
+ }
+ .underline {
+ text-decoration-line: underline;
+ }
+ .opacity-50 {
+ opacity: 50%;
+ }
+ .opacity-75 {
+ opacity: 75%;
+ }
+ .shadow-lg {
+ --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+ .shadow-none {
+ --tw-shadow: 0 0 #0000;
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+ .shadow-sm {
+ --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+ .outline {
+ outline-style: var(--tw-outline-style);
+ outline-width: 1px;
+ }
+ .filter {
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
+ }
+ .transition {
+ transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events;
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
+ .ease-in-out {
+ --tw-ease: var(--ease-in-out);
+ transition-timing-function: var(--ease-in-out);
+ }
+ .ease-out {
+ --tw-ease: var(--ease-out);
+ transition-timing-function: var(--ease-out);
+ }
+ .btn-neutral {
+ --btn-color: var(--color-neutral);
+ --btn-fg: var(--color-neutral-content);
+ }
+ .outline-none {
+ --tw-outline-style: none;
+ outline-style: none;
+ }
+ .group-hover\:flex {
+ &:is(:where(.group):hover *) {
+ @media (hover: hover) {
+ display: flex;
+ }
+ }
+ }
+ .placeholder\:text-base-content {
+ &::placeholder {
+ color: var(--color-base-content);
+ }
+ }
+ .placeholder\:opacity-75 {
+ &::placeholder {
+ opacity: 75%;
+ }
+ }
+ .focus-within\:outline {
+ &:focus-within {
+ outline-style: var(--tw-outline-style);
+ outline-width: 1px;
+ }
+ }
+ .focus-within\:outline-2 {
+ &:focus-within {
+ outline-style: var(--tw-outline-style);
+ outline-width: 2px;
+ }
+ }
+ .focus-within\:outline-primary {
+ &:focus-within {
+ outline-color: var(--color-primary);
+ }
+ }
+ .hover\:bg-base-200 {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-base-200);
+ }
+ }
+ }
+ .hover\:bg-base-300 {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-base-300);
+ }
+ }
+ }
+}
+@layer base {
+ :where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] {
+ color-scheme: light;
+ --color-base-100: oklch(100% 0 0);
+ --color-base-200: oklch(98% 0 0);
+ --color-base-300: oklch(95% 0 0);
+ --color-base-content: oklch(21% 0.006 285.885);
+ --color-primary: oklch(45% 0.24 277.023);
+ --color-primary-content: oklch(93% 0.034 272.788);
+ --color-secondary: oklch(65% 0.241 354.308);
+ --color-secondary-content: oklch(94% 0.028 342.258);
+ --color-accent: oklch(77% 0.152 181.912);
+ --color-accent-content: oklch(38% 0.063 188.416);
+ --color-neutral: oklch(14% 0.005 285.823);
+ --color-neutral-content: oklch(92% 0.004 286.32);
+ --color-info: oklch(74% 0.16 232.661);
+ --color-info-content: oklch(29% 0.066 243.157);
+ --color-success: oklch(76% 0.177 163.223);
+ --color-success-content: oklch(37% 0.077 168.94);
+ --color-warning: oklch(82% 0.189 84.429);
+ --color-warning-content: oklch(41% 0.112 45.904);
+ --color-error: oklch(71% 0.194 13.428);
+ --color-error-content: oklch(27% 0.105 12.094);
+ --radius-selector: 0.5rem;
+ --radius-field: 0.25rem;
+ --radius-box: 0.5rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 1;
+ --noise: 0;
+ }
+}
+@layer base {
+ @media (prefers-color-scheme: dark) {
+ :root {
+ color-scheme: dark;
+ --color-base-100: oklch(25.33% 0.016 252.42);
+ --color-base-200: oklch(23.26% 0.014 253.1);
+ --color-base-300: oklch(21.15% 0.012 254.09);
+ --color-base-content: oklch(97.807% 0.029 256.847);
+ --color-primary: oklch(58% 0.233 277.117);
+ --color-primary-content: oklch(96% 0.018 272.314);
+ --color-secondary: oklch(65% 0.241 354.308);
+ --color-secondary-content: oklch(94% 0.028 342.258);
+ --color-accent: oklch(77% 0.152 181.912);
+ --color-accent-content: oklch(38% 0.063 188.416);
+ --color-neutral: oklch(14% 0.005 285.823);
+ --color-neutral-content: oklch(92% 0.004 286.32);
+ --color-info: oklch(74% 0.16 232.661);
+ --color-info-content: oklch(29% 0.066 243.157);
+ --color-success: oklch(76% 0.177 163.223);
+ --color-success-content: oklch(37% 0.077 168.94);
+ --color-warning: oklch(82% 0.189 84.429);
+ --color-warning-content: oklch(41% 0.112 45.904);
+ --color-error: oklch(71% 0.194 13.428);
+ --color-error-content: oklch(27% 0.105 12.094);
+ --radius-selector: 0.5rem;
+ --radius-field: 0.25rem;
+ --radius-box: 0.5rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 1;
+ --noise: 0;
+ }
+ }
+}
+@layer base {
+ :root:has(input.theme-controller[value=light]:checked),[data-theme=light] {
+ color-scheme: light;
+ --color-base-100: oklch(100% 0 0);
+ --color-base-200: oklch(98% 0 0);
+ --color-base-300: oklch(95% 0 0);
+ --color-base-content: oklch(21% 0.006 285.885);
+ --color-primary: oklch(45% 0.24 277.023);
+ --color-primary-content: oklch(93% 0.034 272.788);
+ --color-secondary: oklch(65% 0.241 354.308);
+ --color-secondary-content: oklch(94% 0.028 342.258);
+ --color-accent: oklch(77% 0.152 181.912);
+ --color-accent-content: oklch(38% 0.063 188.416);
+ --color-neutral: oklch(14% 0.005 285.823);
+ --color-neutral-content: oklch(92% 0.004 286.32);
+ --color-info: oklch(74% 0.16 232.661);
+ --color-info-content: oklch(29% 0.066 243.157);
+ --color-success: oklch(76% 0.177 163.223);
+ --color-success-content: oklch(37% 0.077 168.94);
+ --color-warning: oklch(82% 0.189 84.429);
+ --color-warning-content: oklch(41% 0.112 45.904);
+ --color-error: oklch(71% 0.194 13.428);
+ --color-error-content: oklch(27% 0.105 12.094);
+ --radius-selector: 0.5rem;
+ --radius-field: 0.25rem;
+ --radius-box: 0.5rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 1;
+ --noise: 0;
+ }
+}
+@layer base {
+ :root:has(input.theme-controller[value=dark]:checked),[data-theme=dark] {
+ color-scheme: dark;
+ --color-base-100: oklch(25.33% 0.016 252.42);
+ --color-base-200: oklch(23.26% 0.014 253.1);
+ --color-base-300: oklch(21.15% 0.012 254.09);
+ --color-base-content: oklch(97.807% 0.029 256.847);
+ --color-primary: oklch(58% 0.233 277.117);
+ --color-primary-content: oklch(96% 0.018 272.314);
+ --color-secondary: oklch(65% 0.241 354.308);
+ --color-secondary-content: oklch(94% 0.028 342.258);
+ --color-accent: oklch(77% 0.152 181.912);
+ --color-accent-content: oklch(38% 0.063 188.416);
+ --color-neutral: oklch(14% 0.005 285.823);
+ --color-neutral-content: oklch(92% 0.004 286.32);
+ --color-info: oklch(74% 0.16 232.661);
+ --color-info-content: oklch(29% 0.066 243.157);
+ --color-success: oklch(76% 0.177 163.223);
+ --color-success-content: oklch(37% 0.077 168.94);
+ --color-warning: oklch(82% 0.189 84.429);
+ --color-warning-content: oklch(41% 0.112 45.904);
+ --color-error: oklch(71% 0.194 13.428);
+ --color-error-content: oklch(27% 0.105 12.094);
+ --radius-selector: 0.5rem;
+ --radius-field: 0.25rem;
+ --radius-box: 0.5rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 1;
+ --noise: 0;
+ }
+}
+@layer base {
+ :root {
+ --fx-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E");
+ }
+}
+@layer base {
+ :root, [data-theme] {
+ background-color: var(--root-bg, var(--color-base-100));
+ color: var(--color-base-content);
+ }
+}
+@layer base {
+ :root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not([class*="drawer-open"]) > .drawer-toggle:checked ) {
+ overflow: hidden;
+ }
+}
+@layer base {
+ @property --radialprogress {
+ syntax: "
";
+ inherits: true;
+ initial-value: 0%;
+ }
+}
+@layer base {
+ :where( :root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not(.drawer-open) > .drawer-toggle:checked ) ) {
+ scrollbar-gutter: stable;
+ background-image: linear-gradient(var(--color-base-100), var(--color-base-100));
+ --root-bg: var(--color-base-100);
+ @supports (color: color-mix(in lab, red, red)) {
+ --root-bg: color-mix(in srgb, var(--color-base-100), oklch(0% 0 0) 40%);
+ }
+ }
+ :where(.modal[open], .modal-open, .modal-toggle:checked + .modal):not(.modal-start, .modal-end) {
+ scrollbar-gutter: stable;
+ }
+}
+@layer base {
+ :root {
+ scrollbar-color: currentColor #0000;
+ @supports (color: color-mix(in lab, red, red)) {
+ scrollbar-color: color-mix(in oklch, currentColor 35%, #0000) #0000;
+ }
+ }
+}
+@keyframes progress {
+ 50% {
+ background-position-x: -115%;
+ }
+}
+@keyframes rating {
+ 0%, 40% {
+ scale: 1.1;
+ filter: brightness(1.05) contrast(1.05);
+ }
+}
+@keyframes skeleton {
+ 0% {
+ background-position: 150%;
+ }
+ 100% {
+ background-position: -50%;
+ }
+}
+@keyframes dropdown {
+ 0% {
+ opacity: 0;
+ }
+}
+@keyframes radio {
+ 0% {
+ padding: 5px;
+ }
+ 50% {
+ padding: 3px;
+ }
+}
+@keyframes toast {
+ 0% {
+ scale: 0.9;
+ opacity: 0;
+ }
+ 100% {
+ scale: 1;
+ opacity: 1;
+ }
+}
+@layer base {
+ @media (prefers-color-scheme: dark) {
+ :root {
+ color-scheme: dark;
+ --color-base-100: oklch(14% 0 0);
+ --color-base-200: oklch(20% 0 0);
+ --color-base-300: oklch(26% 0 0);
+ --color-base-content: oklch(97% 0 0);
+ --color-primary: oklch(55% 0.013 58.071);
+ --color-primary-content: oklch(98% 0.001 106.423);
+ --color-secondary: oklch(60% 0.25 292.717);
+ --color-secondary-content: oklch(96% 0.016 293.756);
+ --color-accent: oklch(58% 0.233 277.117);
+ --color-accent-content: oklch(96% 0.018 272.314);
+ --color-neutral: oklch(20% 0 0);
+ --color-neutral-content: oklch(98% 0 0);
+ --color-info: oklch(74% 0.16 232.661);
+ --color-info-content: oklch(29% 0.066 243.157);
+ --color-success: oklch(76% 0.177 163.223);
+ --color-success-content: oklch(26% 0.051 172.552);
+ --color-warning: oklch(82% 0.189 84.429);
+ --color-warning-content: oklch(27% 0.077 45.635);
+ --color-error: oklch(70% 0.191 22.216);
+ --color-error-content: oklch(25% 0.092 26.042);
+ --radius-selector: 1rem;
+ --radius-field: 2rem;
+ --radius-box: 0.5rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 1;
+ --noise: 0;
+ }
+ }
+}
+@layer base {
+ :where(:root),:root:has(input.theme-controller[value=synthwave]:checked),[data-theme="synthwave"] {
+ color-scheme: dark;
+ --color-base-100: oklch(14% 0 0);
+ --color-base-200: oklch(20% 0 0);
+ --color-base-300: oklch(26% 0 0);
+ --color-base-content: oklch(97% 0 0);
+ --color-primary: oklch(55% 0.013 58.071);
+ --color-primary-content: oklch(98% 0.001 106.423);
+ --color-secondary: oklch(60% 0.25 292.717);
+ --color-secondary-content: oklch(96% 0.016 293.756);
+ --color-accent: oklch(58% 0.233 277.117);
+ --color-accent-content: oklch(96% 0.018 272.314);
+ --color-neutral: oklch(20% 0 0);
+ --color-neutral-content: oklch(98% 0 0);
+ --color-info: oklch(74% 0.16 232.661);
+ --color-info-content: oklch(29% 0.066 243.157);
+ --color-success: oklch(76% 0.177 163.223);
+ --color-success-content: oklch(26% 0.051 172.552);
+ --color-warning: oklch(82% 0.189 84.429);
+ --color-warning-content: oklch(27% 0.077 45.635);
+ --color-error: oklch(70% 0.191 22.216);
+ --color-error-content: oklch(25% 0.092 26.042);
+ --radius-selector: 1rem;
+ --radius-field: 2rem;
+ --radius-box: 0.5rem;
+ --size-selector: 0.25rem;
+ --size-field: 0.25rem;
+ --border: 1px;
+ --depth: 1;
+ --noise: 0;
+ }
+}
+@property --tw-scale-x {
+ syntax: "*";
+ inherits: false;
+ initial-value: 1;
+}
+@property --tw-scale-y {
+ syntax: "*";
+ inherits: false;
+ initial-value: 1;
+}
+@property --tw-scale-z {
+ syntax: "*";
+ inherits: false;
+ initial-value: 1;
+}
+@property --tw-rotate-x {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-rotate-y {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-rotate-z {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-skew-x {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-skew-y {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-border-style {
+ syntax: "*";
+ inherits: false;
+ initial-value: solid;
+}
+@property --tw-font-weight {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-shadow-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-shadow-alpha {
+ syntax: "";
+ inherits: false;
+ initial-value: 100%;
+}
+@property --tw-inset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-inset-shadow-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-inset-shadow-alpha {
+ syntax: "";
+ inherits: false;
+ initial-value: 100%;
+}
+@property --tw-ring-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-inset-ring-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-inset-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-ring-inset {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-ring-offset-width {
+ syntax: "";
+ inherits: false;
+ initial-value: 0px;
+}
+@property --tw-ring-offset-color {
+ syntax: "*";
+ inherits: false;
+ initial-value: #fff;
+}
+@property --tw-ring-offset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-outline-style {
+ syntax: "*";
+ inherits: false;
+ initial-value: solid;
+}
+@property --tw-blur {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-brightness {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-contrast {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-grayscale {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-hue-rotate {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-invert {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-opacity {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-saturate {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-sepia {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-drop-shadow {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-drop-shadow-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-drop-shadow-alpha {
+ syntax: "";
+ inherits: false;
+ initial-value: 100%;
+}
+@property --tw-drop-shadow-size {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-ease {
+ syntax: "*";
+ inherits: false;
+}
+@layer properties {
+ @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
+ *, ::before, ::after, ::backdrop {
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-scale-z: 1;
+ --tw-rotate-x: initial;
+ --tw-rotate-y: initial;
+ --tw-rotate-z: initial;
+ --tw-skew-x: initial;
+ --tw-skew-y: initial;
+ --tw-border-style: solid;
+ --tw-font-weight: initial;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-color: initial;
+ --tw-shadow-alpha: 100%;
+ --tw-inset-shadow: 0 0 #0000;
+ --tw-inset-shadow-color: initial;
+ --tw-inset-shadow-alpha: 100%;
+ --tw-ring-color: initial;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-inset-ring-color: initial;
+ --tw-inset-ring-shadow: 0 0 #0000;
+ --tw-ring-inset: initial;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-outline-style: solid;
+ --tw-blur: initial;
+ --tw-brightness: initial;
+ --tw-contrast: initial;
+ --tw-grayscale: initial;
+ --tw-hue-rotate: initial;
+ --tw-invert: initial;
+ --tw-opacity: initial;
+ --tw-saturate: initial;
+ --tw-sepia: initial;
+ --tw-drop-shadow: initial;
+ --tw-drop-shadow-color: initial;
+ --tw-drop-shadow-alpha: 100%;
+ --tw-drop-shadow-size: initial;
+ --tw-ease: initial;
+ }
+ }
+}
diff --git a/wco/privatri/privatri.js b/wco/privatri/privatri.js
index f5fadd3..f1d28b5 100644
--- a/wco/privatri/privatri.js
+++ b/wco/privatri/privatri.js
@@ -1,194 +1,506 @@
-/**
- * @file privatri.js
- * @description WCO component for managing private, thread-based messages in IndexedDB.
- * @version 1.0
- */
+import apx from "./apx.js";
+import { getOldestPrivatriids, syncronizeBackend } from "./utils.js";
-((window) => {
- 'use strict';
+const bodyEl = document.querySelector("body");
+const searchInputEl = document.querySelector("#threadSearchBar");
+const threadFilterOptionsElArray = document.querySelector("#threadFilterOptions").querySelectorAll("li");
+const threadsContainerEl = document.querySelector("#threadsContainer");
+const threadPageEl = document.querySelector("#threadPage");
+const messageInputEl = document.getElementById("messageInput");
+const messagesContainerEl = document.querySelector("#messagesContainer");
+const attachmentsInputEl = document.querySelector("#attachmentsInput");
- // --- Component Definition ---
- const privatri = {};
+function formatDate(timestamp) {
+ const date = new Date(timestamp);
+ const day = String(date.getDate()).padStart(2, "0");
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const year = date.getFullYear();
+ const hours = String(date.getHours()).padStart(2, "0");
+ const minutes = String(date.getMinutes()).padStart(2, "0");
- // --- Private State ---
- let _db = null; // Holds the single, persistent IndexedDB connection
- const DB_NAME = 'privatriDB';
- const DB_VERSION = 1;
+ return `${day}/${month}/${year} ${hours}:${minutes}`;
+};
- /**
- * Logs messages to the console with a consistent prefix.
- * @param {string} level - The log level (e.g., 'log', 'error', 'warn').
- * @param {...any} args - The messages to log.
- */
- const _log = (level, ...args) => {
- console[level]('[privatri]', ...args);
- };
+searchInputEl.addEventListener("input", () => {
+ const value = searchInputEl.value.toLowerCase();
- /**
- * Opens and initializes the IndexedDB database.
- * This function is called by init() and handles the connection and schema upgrades.
- * @param {string[]} initialStoreNames - An array of store names (thread IDs) to ensure exist.
- * @returns {Promise} A promise that resolves with the database connection.
- */
- const _openDatabase = (initialStoreNames = []) => {
- return new Promise((resolve, reject) => {
- _log('log', `Opening database "${DB_NAME}" with version ${DB_VERSION}.`);
-
- const request = indexedDB.open(DB_NAME, DB_VERSION);
-
- // This event is only triggered for new databases or version changes.
- request.onupgradeneeded = (event) => {
- const db = event.target.result;
- _log('log', 'Database upgrade needed. Current stores:', [...db.objectStoreNames]);
-
- initialStoreNames.forEach(storeName => {
- if (!db.objectStoreNames.contains(storeName)) {
- _log('log', `Creating new object store: "${storeName}"`);
- db.createObjectStore(storeName, { keyPath: 'key' });
- }
- });
- };
-
- request.onsuccess = (event) => {
- _log('log', 'Database opened successfully.');
- _db = event.target.result;
-
- // Generic error handler for the connection
- _db.onerror = (event) => {
- _log('error', 'Database error:', event.target.error);
+ Array.from(threadsContainerEl.children).forEach(child => {
+ if (child.querySelector("li > div").textContent.toLowerCase().includes(value)) {
+ child.style.display = "block";
+ } else {
+ child.style.display = "none";
};
+ });
+});
+
+threadFilterOptionsElArray.forEach(option => {
+ option.addEventListener("click", async () => {
+ const filter = option.getAttribute("data-filter");
+
+ if (filter === "date") {
+ Array.from(threadsContainerEl.children).sort((a, b) => {
+ return a.querySelector("li > span").getAttribute("data-timestamp") - b.querySelector("li > span").getAttribute("data-timestamp");
+ }).forEach(child => {
+ threadsContainerEl.appendChild(child);
+ });
+ } else if (filter === "name") {
+ Array.from(threadsContainerEl.children).sort((a, b) => {
+ return a.querySelector("li > div").textContent.localeCompare(b.querySelector("li > div").textContent);
+ }).forEach(child => {
+ threadsContainerEl.appendChild(child);
+ });
+ };
+ });
+});
+
+const sendNewMessage = async (timestamp, message, attachmentsArray) => {
+ let messageTemplate = "";
+
+ await fetch("./message.mustache")
+ .then(res => res.text())
+ .then(template => {
+ messageTemplate = template;
+ });
+
+ messageTemplate = Mustache.render(messageTemplate, { timestamp, message, date: formatDate(timestamp) });
+
+ const tempNode = document.createElement("div");
+ tempNode.innerHTML = messageTemplate;
+ const messageNode = tempNode.firstElementChild;
+
+ attachmentsArray.forEach(attachment => {
+ messageNode.querySelector("div.attachmentsContainer").insertAdjacentHTML("beforeend", ` `);
+ });
+
+ return messageNode.outerHTML;
+};
+
+const editMessage = async (timestamp, message) => {
+ let editMessageTemplate = "";
+
+ await fetch("./editMessage.mustache")
+ .then(res => res.text())
+ .then(template => {
+ editMessageTemplate = template;
+ });
+
+ return Mustache.render(editMessageTemplate, { message, date: formatDate(timestamp) });
+};
+
+const displayToastAlert = async (message) => {
+ let toastAlertTemplate = "";
+
+ await fetch("./toastAlert.mustache")
+ .then(res => res.text())
+ .then(template => {
+ toastAlertTemplate = template;
+ });
+
+ return Mustache.render(toastAlertTemplate, { message });
+};
+
+sendMessageBtn.addEventListener("click", async () => {
+ const message = messageInputEl.value.trim();
+
+ if (message !== "") {
+ const messageObj = await (async (publicKey) => {
+ const timestamp = Date.now();
+ const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("apx")).data.headers.xalias, publicKey);
+
+ return {
+ privatriid: `${threadPageEl.getAttribute("data-uuid")}_${timestamp}`,
+ timestamp: timestamp,
+ sender_alias: alias,
+ message: await apx.crypto.encryptMessage(message, publicKey),
+ attachments: await (async () => {
+ const attachmentsArray = [];
+ const localAttachmentsArray = JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`)) || [];
+
+ if (localAttachmentsArray.length > 0) {
+ for (const attachment of localAttachmentsArray) {
+ attachmentsArray.push(
+ {
+ fileType: attachment.fileType,
+ filename: attachment.filename,
+ content: await apx.crypto.encryptMessage(attachment.content, publicKey)
+ }
+ );
+ };
+ };
+
+ return attachmentsArray;
+ })()
+ };
+ })((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messagesContainerEl.firstElementChild.getAttribute("data-timestamp")}`)).publicKey);
- resolve(_db);
- };
+ const newMessageHTML = await sendNewMessage(messageObj.timestamp, message, JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`)) || []);
- request.onerror = (event) => {
- _log('error', 'Failed to open database:', event.target.error);
- reject(event.target.error);
- };
+ sessionStorage.removeItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`);
+
+ messagesContainerEl.insertAdjacentHTML("beforeend", newMessageHTML);
+
+ messageInputEl.value = "";
+
+ // Faire un post sur l'endpoint /privatri
+
+ await apx.indexedDB.set("privatri", "messages", messageObj);
+ };
+});
+
+document.addEventListener("DOMContentLoaded", async () => {
+ const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now();
+
+ await syncronizeBackend(lastConnection);
+
+ const privatriidArray = await getOldestPrivatriids("privatri", "messages");
+
+ const thread = async (name, uuid) => {
+ let threadTemplate = "";
+
+ await fetch("./thread.mustache")
+ .then(res => res.text())
+ .then(template => {
+ threadTemplate = template;
+ });
+
+ return Mustache.render(threadTemplate, { uuid, name });
+ };
+
+ for (const privatriid of privatriidArray) {
+ const obj = await apx.indexedDB.get("privatri", "messages", privatriid)
+
+ const privateKey = (await apx.indexedDB.get("privatri", "threads", obj.thread)).privateKey;
+ const name = (await apx.crypto.decryptMessage(obj.title, privateKey)).data;
+
+ threadsContainerEl.insertAdjacentHTML("beforeend", await thread(name, obj.thread));
+ };
+
+ Array.from(threadsContainerEl.children).forEach(child => {
+ child.addEventListener("click", async () => {
+ const uuid = child.getAttribute("data-uuid");
+
+ const messagesObj = await new Promise((resolve, reject) => {
+ const request = indexedDB.open("privatri", 1);
+
+ request.onsuccess = (event) => {
+ const db = event.target.result;
+
+ const transaction = db.transaction("messages", "readonly");
+ const store = transaction.objectStore("messages");
+ const cursorRequest = store.openCursor();
+
+ const result = [];
+ cursorRequest.onsuccess = (event) => {
+ const cursor = event.target.result;
+
+ if (cursor) {
+ const obj = cursor.value;
+ const keyUuid = obj.privatriid.split("_")[0];
+
+ if (keyUuid === uuid) {
+ result.push(obj);
+ };
+ cursor.continue();
+ } else {
+ resolve(result);
+ };
+ };
+
+ cursorRequest.onerror = (event) => reject(event);
+ };
+
+ request.onerror = (event) => reject(event);
+ });
+
+ threadPageEl.setAttribute("data-uuid", uuid);
+ threadPageEl.querySelector("#threadName").innerText = (await apx.crypto.decryptMessage(messagesObj[0].title, (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey)).data;
+
+ messagesContainerEl.innerHTML = "";
+
+ let privateKey = "";
+
+ for (const message of messagesObj) {
+ if (privateKey === "") {
+ privateKey = (await apx.indexedDB.get("privatri", "threads", message.thread)).privateKey;
+ };
+
+ const decryptedMessage = (await apx.crypto.decryptMessage(message.message, privateKey)).data;
+
+ let decryptedAttachments = [];
+
+ if (message.attachments !== undefined) {
+ decryptedAttachments = await (async () => {
+ const attachmentsArray = [];
+
+ if (message.attachments.length > 0) {
+ for (const attachment of message.attachments) {
+ attachmentsArray.push({
+ fileType: attachment.fileType,
+ filename: attachment.filename,
+ content: (await apx.crypto.decryptMessage(attachment.content, privateKey)).data
+ });
+ };
+ };
+
+ return attachmentsArray;
+ })();
+ };
+
+ messagesContainerEl.insertAdjacentHTML("beforeend", await sendNewMessage(message.timestamp, decryptedMessage, decryptedAttachments));
+ };
+ });
});
- };
- // --- Public API ---
+ document.querySelectorAll("a").forEach(link => {
+ link.addEventListener("click", async (event) => {
+ event.preventDefault();
- /**
- * Initializes the privatri component.
- * Opens the database connection and ensures initial object stores are created.
- * @param {object} config - Configuration object.
- * @param {string[]} [config.threads=[]] - An array of initial thread IDs (store names) to create.
- * @returns {Promise}
- */
- privatri.init = async (config = {}) => {
- if (_db) {
- _log('warn', 'privatri component already initialized.');
- return;
- }
- const threads = config.threads || [];
- await _openDatabase(threads);
- };
+ window.history.replaceState({}, document.title, window.location.pathname);
- /**
- * Retrieves a value from a specific store (thread).
- * @param {string} storeName - The name of the store (thread ID).
- * @param {string} key - The key of the item to retrieve.
- * @returns {Promise} A promise that resolves with the value or null if not found.
- */
- privatri.getValue = (storeName, key) => {
- return new Promise((resolve, reject) => {
- if (!_db || !_db.objectStoreNames.contains(storeName)) {
- _log('warn', `Store "${storeName}" does not exist.`);
- return resolve(null);
- }
- const transaction = _db.transaction(storeName, 'readonly');
- const store = transaction.objectStore(storeName);
- const request = store.get(key);
+ const templateName = link.getAttribute("data-template");
- request.onsuccess = () => resolve(request.result ? request.result.value : null);
- request.onerror = (e) => {
- _log('error', `Error getting value for key "${key}" from store "${storeName}":`, e.target.error);
- reject(e.target.error);
- };
+ if (templateName === "threadAliasList" || templateName === "threadSettings") {
+ window.history.pushState({}, "", `${window.location.pathname}?uuid=${threadPageEl.getAttribute("data-uuid")}`);
+ };
+
+ await fetch(`./${templateName}.mustache`)
+ .then(res => res.text())
+ .then(template => {
+ template = Mustache.render(template, {});
+
+ bodyEl.innerHTML = template;
+ });
+
+ const script = document.createElement("script");
+
+ script.type = "module";
+ script.src = `./${templateName}.js`;
+
+ bodyEl.appendChild(script);
+ });
});
- };
+});
- /**
- * Adds or updates a key-value pair in a specific store (thread).
- * @param {string} storeName - The name of the store (thread ID).
- * @param {string} key - The key of the item to set.
- * @param {any} value - The value to store.
- * @returns {Promise}
- */
- privatri.setValue = (storeName, key, value) => {
- return new Promise((resolve, reject) => {
- if (!_db || !_db.objectStoreNames.contains(storeName)) {
- _log('error', `Cannot set value. Store "${storeName}" does not exist.`);
- return reject(new Error(`Store "${storeName}" not found.`));
- }
- const transaction = _db.transaction(storeName, 'readwrite');
- const store = transaction.objectStore(storeName);
- const request = store.put({ key: key, value: value });
+window.addEventListener("beforeunload", () => {
+ if (apx)
+ localStorage.setItem("lastConnection", JSON.stringify(Date.now()));
+});
- request.onsuccess = () => resolve();
- request.onerror = (e) => {
- _log('error', `Error setting value for key "${key}" in store "${storeName}":`, e.target.error);
- reject(e.target.error);
- };
- });
- };
+window.attachDeleteMessageEvent = async function attachDeleteMessageEvent(btn) {
+ const messageEl = btn.parentElement.parentElement.parentElement;
- /**
- * Removes a key-value pair from a specific store (thread).
- * @param {string} storeName - The name of the store (thread ID).
- * @param {string} key - The key of the item to remove.
- * @returns {Promise}
- */
- privatri.removeKey = (storeName, key) => {
- return new Promise((resolve, reject) => {
- if (!_db || !_db.objectStoreNames.contains(storeName)) {
- _log('warn', `Cannot remove key. Store "${storeName}" does not exist.`);
- return resolve(); // Resolve peacefully if store doesn't exist
- }
- const transaction = _db.transaction(storeName, 'readwrite');
- const store = transaction.objectStore(storeName);
- const request = store.delete(key);
+ const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey;
- request.onsuccess = () => resolve();
- request.onerror = (e) => {
- _log('error', `Error removing key "${key}" from store "${storeName}":`, e.target.error);
- reject(e.target.error);
- };
- });
- };
+ const signatureMessage = `${JSON.parse(localStorage.getItem("apx")).data.headers.xalias}_${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`;
+ const signature = await apx.crypto.sign(signatureMessage, privateKey);
+
+ let verified = false;
- /**
- * A utility function to save a batch of messages, demonstrating how to use the component.
- * @param {object} threadsObj - An object where keys are thread IDs and values are message objects.
- * @example
- * const messages = {
- * "thread-123": { "1678886400": { text: "Hello" } },
- * "thread-456": { "1678886401": { text: "Hi there" } }
- * };
- * await privatri.storeMessages(messages);
- */
- privatri.storeMessages = async (threadsObj = {}) => {
- if (!_db) {
- _log('error', 'Database not initialized. Please call privatri.init() first.');
- return;
- }
- _log('log', 'Storing messages in IndexedDB...');
- for (const [uuid, threadObj] of Object.entries(threadsObj)) {
- // Ensure the object store exists before trying to write to it.
- if (!_db.objectStoreNames.contains(uuid)) {
- _log('warn', `Store "${uuid}" not found during message storage. You may need to re-init with this thread.`);
- continue;
- }
- for (const [timestamp, messageObj] of Object.entries(threadObj)) {
- await privatri.setValue(uuid, timestamp, messageObj);
- }
- }
- _log('log', 'Finished storing messages.');
- };
+ try {
+ verified = await fetch("", {
+ method: "GET",
+ body: JSON.stringify({
+ message: signatureMessage,
+ signature: signature,
+ uuid: threadPageEl.getAttribute("data-uuid"),
+ timestamp: messageEl.getAttribute("data-timestamp"),
+ })
+ });
+ } catch (error) {
+ console.error("Error while verifying signature:", error);
+ };
- // Expose the component to the global window object
- window.privatri = privatri;
+ const authorAlias = await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, privateKey).data;
-})(window);
+ if ((JSON.parse(localStorage.getItem("apx")).data.headers.xalias === authorAlias) && (verified === true)) {
+ await apx.indexedDB.del("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`);
+
+ messageEl.remove();
+ } else {
+ bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to delete this message."));
+
+ setTimeout(() => {
+ bodyEl.lastElementChild.remove();
+ }, 3000);
+ };
+};
+
+window.attachEditMessageEvent = async function attachEditMessageEvent(btn) {
+ const messageEl = btn.parentElement.parentElement.parentElement;
+
+ const authorAlias = (await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey)).data;
+
+ if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === authorAlias) {
+ const messageValue = messageEl.querySelector("div.message").innerText;
+
+ const attachmentsArray = (() => {
+ const attachmentsArray = [];
+
+ messageEl.querySelector("div.attachmentsContainer").querySelectorAll("img").forEach(img => {
+ attachmentsArray.push(
+ {
+ fileType: img.getAttribute("src").match(/^data:(.*);base64,/)[1],
+ filename: img.getAttribute("alt"),
+ content: img.getAttribute("src").split(",")[1]
+ }
+ );
+ });
+
+ return attachmentsArray;
+ })();
+
+ messageEl.innerHTML = await editMessage(parseInt(messageEl.getAttribute("data-timestamp"), 10), messageEl.querySelector("div.message").innerText);
+
+ messageEl.querySelector("button.cancelEditBtn").addEventListener("click", async () => {
+ const messageObj = await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`);
+
+ messageEl.innerHTML = await sendNewMessage(messageObj.timestamp, messageValue, attachmentsArray);
+ });
+
+ messageEl.querySelector("button.saveEditBtn").addEventListener("click", async () => {
+ const newMessageValue = messageEl.querySelector("textarea").value.trim();
+
+ if (newMessageValue !== "") {
+ const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey;
+
+ const messageObj = await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`);
+
+ messageObj.message = await apx.crypto.encryptMessage(newMessageValue, privateKey),
+
+ await apx.indexedDB.set("privatri", "messages", messageObj);
+
+ messageEl.innerHTML = await sendNewMessage(messageObj.timestamp, newMessageValue, attachmentsArray);
+ };
+ });
+ } else {
+ bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to edit this message."));
+
+ setTimeout(() => {
+ bodyEl.lastElementChild.remove();
+ }, 3000);
+ };
+};
+
+window.attachEventDisplayAttachment = function(img) {
+ let overlay = document.getElementById("imageOverlay");
+
+ if (overlay === null) {
+ overlay = document.createElement('div');
+
+ overlay.id = "imageOverlay";
+ overlay.style.position = "fixed";
+ overlay.style.top = 0;
+ overlay.style.left = 0;
+ overlay.style.width = "100vw";
+ overlay.style.height = "100vh";
+ overlay.style.background = "rgba(0, 0, 0, 0.75)";
+ overlay.style.display = "flex";
+ overlay.style.alignItems = "center";
+ overlay.style.justifyContent = "center";
+ overlay.style.zIndex = 999;
+
+ overlay.onclick = () => {
+ overlay.remove();
+ };
+
+ document.body.appendChild(overlay);
+ } else {
+ overlay.innerHTML = "";
+ overlay.style.display = "flex";
+ };
+
+ const fullScreenImage = document.createElement("img");
+
+ fullScreenImage.src = img.src;
+ fullScreenImage.alt = img.alt;
+ fullScreenImage.style.maxWidth = "90vw";
+ fullScreenImage.style.maxHeight = "90vh";
+ overlay.appendChild(fullScreenImage);
+};
+
+document.querySelector("#attachmentsBtn").addEventListener("click", () => {
+ attachmentsInputEl.click();
+});
+
+attachmentsInputEl.addEventListener("change", async () => {
+ const filesArray = Array.from(attachmentsInputEl.files);
+ const maxFileSize = 5 * 1024 * 1024;
+ const maxSize = 512;
+
+ for (const file of filesArray) {
+ if (file.size <= maxFileSize) {
+ const attachmentObj = await new Promise((resolve, reject) => {
+ const attachmentObj = {
+ fileType: file.type,
+ filename: file.name,
+ content: ""
+ };
+
+ const img = new Image();
+
+ img.onload = () => {
+ if (img.width > maxSize || img.height > maxSize) {
+ let width = img.width;
+ let height = img.height;
+
+ if (width > maxSize) {
+ height *= maxSize / width;
+ width = maxSize;
+ };
+
+ if (height > maxSize) {
+ width *= maxSize / height;
+ height = maxSize;
+ };
+
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+
+ const ctx = canvas.getContext("2d");
+
+ ctx.drawImage(img, 0, 0, width, height);
+
+ canvas.toBlob(blob => {
+ const reader = new FileReader();
+
+ reader.onload = (event) => {
+ attachmentObj.content = event.target.result.split(",")[1];
+
+ resolve(attachmentObj);
+ };
+
+ reader.onerror = reject;
+ reader.readAsDataURL(blob);
+ }, "image/jpeg", 0.8);
+ } else {
+ const reader = new FileReader();
+
+ reader.onload = (event) => {
+ attachmentObj.content = event.target.result.split(",")[1];
+
+ resolve(attachmentObj);
+ };
+
+ reader.onerror = reject;
+ reader.readAsDataURL(file);
+ };
+ };
+
+ img.onerror = reject;
+ img.src = URL.createObjectURL(file);
+
+ return attachmentObj;
+ });
+
+ if (sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`) === null) {
+ sessionStorage.setItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`, JSON.stringify([attachmentObj]));
+ } else {
+ const attachmentsArray = JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`));
+ attachmentsArray.push(attachmentObj);
+
+ sessionStorage.setItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`, JSON.stringify(attachmentsArray));
+ };
+ };
+ };
+});
\ No newline at end of file
diff --git a/wco/privatri/thread.mustache b/wco/privatri/thread.mustache
new file mode 100644
index 0000000..2103725
--- /dev/null
+++ b/wco/privatri/thread.mustache
@@ -0,0 +1,7 @@
+
+
+
+ {{name}}
+ 17
+
+
\ No newline at end of file
diff --git a/wco/privatri/threadAliasList.js b/wco/privatri/threadAliasList.js
new file mode 100644
index 0000000..9456e0a
--- /dev/null
+++ b/wco/privatri/threadAliasList.js
@@ -0,0 +1,88 @@
+import apx from "./apx.js";
+import { getOldestPrivatriids } from "./utils.js";
+
+const bodyEl = document.querySelector("body");
+const aliasListContainerEl = document.querySelector("#aliasListContainer");
+
+let aliasesArray = [];
+
+const newAlias = async (alias) => {
+ let aliasTemplate = "";
+
+ await fetch("./alias.mustache")
+ .then(res => res.text())
+ .then(template => {
+ aliasTemplate = template;
+ });
+
+ return Mustache.render(aliasTemplate, { alias });
+};
+
+const displayToastAlert = async (message) => {
+ let toastAlertTemplate = "";
+
+ await fetch("./toastAlert.mustache")
+ .then(res => res.text())
+ .then(template => {
+ toastAlertTemplate = template;
+ });
+
+ return Mustache.render(toastAlertTemplate, { message });
+};
+
+(async () => {
+ const params = new URLSearchParams(window.location.search);
+ const uuid = params.get("uuid");
+
+ const privateKey = (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey;
+
+ const privatriidArray = await getOldestPrivatriids("privatri", "messages");
+
+ privatriidArray.forEach(async privatriid => {
+ if (privatriid.split("_")[0] === uuid) {
+ aliasesArray = (await apx.indexedDB.get("privatri", "messages", privatriid)).aliases;
+ const ownerAlias = (await apx.indexedDB.get("privatri", "messages", privatriid)).owner;
+
+ for (const alias of aliasesArray) {
+ aliasListContainerEl.insertAdjacentHTML("beforeend", await newAlias({
+ decrypted: (await apx.crypto.decryptMessage(alias, privateKey)).data,
+ crypted: alias
+ }));
+ };
+
+ document.querySelectorAll("button.removeAliasBtn").forEach(btn => {
+ btn.addEventListener("click", async () => {
+ if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) {
+ if (btn.getAttribute("data-alias") !== ownerAlias) {
+ const alias = btn.getAttribute("data-alias");
+ aliasesArray = aliasesArray.filter(a => a !== alias);
+ btn.parentElement.parentElement.remove();
+
+ privatriidArray.forEach(async privatriid => {
+ if (privatriid.split("_")[0] === uuid) {
+ const messageObj = await apx.indexedDB.get("privatri", "messages", privatriid);
+ messageObj.aliases = aliasesArray;
+
+ await apx.indexedDB.set("privatri", "messages", messageObj);
+ };
+ });
+ } else {
+ bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You cannot remove the owner of the thread."));
+
+ setTimeout(() => {
+ bodyEl.lastElementChild.remove();
+ }, 3000);
+ };
+ } else {
+ bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to remove this alias."));
+
+ setTimeout(() => {
+ bodyEl.lastElementChild.remove();
+ }, 3000);
+ };
+ });
+ });
+ };
+ });
+})();
+
diff --git a/wco/privatri/threadAliasList.mustache b/wco/privatri/threadAliasList.mustache
new file mode 100644
index 0000000..a42e90a
--- /dev/null
+++ b/wco/privatri/threadAliasList.mustache
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/wco/privatri/threadSettings.js b/wco/privatri/threadSettings.js
new file mode 100644
index 0000000..22ac9c9
--- /dev/null
+++ b/wco/privatri/threadSettings.js
@@ -0,0 +1,72 @@
+import apx from "./apx.js";
+import { getOldestPrivatriids } from "./utils.js";
+
+const bodyEl = document.querySelector("body");
+const threadNameInputEl = document.querySelector("#threadNameInput");
+const autoDeletionBtnElArray = document.querySelectorAll("li.autoDeletionBtn");
+const applyModificationsBtnEl = document.querySelector("#applyModificationsBtn");
+
+const displayToastAlert = async (message) => {
+ let toastAlertTemplate = "";
+
+ await fetch("./toastAlert.mustache")
+ .then(res => res.text())
+ .then(template => {
+ toastAlertTemplate = template;
+ });
+
+ return Mustache.render(toastAlertTemplate, { message });
+};
+
+let messageObj = {};
+
+(async () => {
+ const params = new URLSearchParams(window.location.search);
+ const uuid = params.get("uuid");
+
+ const privateKey = (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey;
+
+ const privatriidArray = await getOldestPrivatriids("privatri", "messages");
+ let ownerAlias = "";
+
+ privatriidArray.forEach(async privatriid => {
+ if (privatriid.split("_")[0] === uuid) {
+ messageObj = await apx.indexedDB.get("privatri", "messages", privatriid);
+ ownerAlias = messageObj.owner;
+
+ threadNameInputEl.value = (await apx.crypto.decryptMessage(messageObj.title, privateKey)).data;
+
+ (Array.from(autoDeletionBtnElArray).find(el => el.getAttribute("data-auto-deletion") === String(messageObj.dt_autodestruction))).classList.add("bg-base-200");
+
+ if (messageObj.urgencydeletion === true) {
+ document.querySelector('input[type="checkbox"].toggle').checked = true;
+ };
+ };
+ });
+
+ applyModificationsBtnEl.addEventListener("click", async () => {
+ if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) {
+ messageObj.title = await apx.crypto.encryptMessage(threadNameInputEl.value, privateKey);
+ messageObj.dt_autodestruction = (() => {
+ const selectedBtn = Array.from(autoDeletionBtnElArray).find(btn => btn.classList.contains("bg-base-200"));
+ return parseInt(selectedBtn ? selectedBtn.getAttribute("data-auto-deletion") : 0, 10);
+ })();
+ messageObj.urgencydeletion = document.querySelector('input[type="checkbox"].toggle').checked;
+
+ await apx.indexedDB.set("privatri", "messages", messageObj);
+ } else {
+ bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to edit this thread."));
+
+ setTimeout(() => {
+ bodyEl.lastElementChild.remove();
+ }, 3000);
+ };
+ });
+})();
+
+autoDeletionBtnElArray.forEach(btn => {
+ btn.addEventListener("click", () => {
+ autoDeletionBtnElArray.forEach(btn => btn.classList.remove("bg-base-200"));
+ btn.classList.add("bg-base-200");
+ });
+});
diff --git a/wco/privatri/threadSettings.mustache b/wco/privatri/threadSettings.mustache
new file mode 100644
index 0000000..3ea3a91
--- /dev/null
+++ b/wco/privatri/threadSettings.mustache
@@ -0,0 +1,33 @@
+
+
Thread settings
+
+
+
+ Message auto destruction
+
+
+
+
+
+
+
+
Apply modifications
+
\ No newline at end of file
diff --git a/wco/privatri/toastAlert.mustache b/wco/privatri/toastAlert.mustache
new file mode 100644
index 0000000..00a4393
--- /dev/null
+++ b/wco/privatri/toastAlert.mustache
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/wco/privatri/utils.js b/wco/privatri/utils.js
new file mode 100644
index 0000000..3eb0d02
--- /dev/null
+++ b/wco/privatri/utils.js
@@ -0,0 +1,46 @@
+async function getOldestPrivatriids(dbName, storeName) {
+ return new Promise((resolve, reject) => {
+ const request = indexedDB.open(dbName, 1);
+
+ request.onsuccess = (event) => {
+ const db = event.target.result;
+
+ const transaction = db.transaction(storeName, "readonly");
+ const store = transaction.objectStore(storeName);
+ const cursorRequest = store.openCursor();
+ const uuidMap = {};
+
+ cursorRequest.onsuccess = (event) => {
+ const cursor = event.target.result;
+ if (cursor) {
+ const obj = cursor.value;
+
+ const [uuid, timestamp] = obj.privatriid.split("_");
+
+ if (!uuidMap[uuid] || Number(timestamp) < uuidMap[uuid].timestamp) {
+ uuidMap[uuid] = { privatriid: obj.privatriid, timestamp: Number(timestamp) };
+ };
+
+ cursor.continue();
+ } else {
+ const result = Object.values(uuidMap).map(event => event.privatriid);
+
+ resolve(result);
+ };
+ };
+
+ cursorRequest.onerror = (event) => reject(event);
+ };
+
+ request.onerror = (event) => reject(event);
+ });
+};
+
+async function syncronizeBackend(lastConnection) {
+
+};
+
+export {
+ getOldestPrivatriids,
+ syncronizeBackend
+};
\ No newline at end of file