Compare commits

...

8 commits

Author SHA1 Message Date
isUnknown
d9f3ede661 feat: persist inheritance lock state per element via data attribute
- Store unlock state in data-inheritance-unlocked attribute on DOM element
- Each element/page now remembers its own inheritance state
- Re-locking removes element-specific CSS block to restore inheritance
- Elements revert to general styles from TextSettings/PageSettings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 12:04:58 +01:00
isUnknown
668d950518 fix: auto-create CSS rules when selector or property is missing
updateCssValue now handles three cases:
- Selector doesn't exist: creates the full block with property
- Selector exists but property missing: adds property to block
- Property exists: updates the value

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 11:59:09 +01:00
isUnknown
681517db21 refactor: extract debounce logic into shared composable
- Create useDebounce composable to avoid code duplication
- Apply debounce to TextSettings margin/padding inputs
- Harmonize debounce delay to 500ms across all components
- Fix input lag when typing values like "30mm"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 11:51:53 +01:00
isUnknown
35c9ab1d3b merge 2025-12-10 11:35:59 +01:00
isUnknown
ceaf318272 improve default styles 2025-12-10 11:35:37 +01:00
Julie Blanc
ea755a2dc6 colors on label 2025-12-10 11:10:14 +01:00
Julie Blanc
718aae2c23 styles w/ differents colors 2025-12-09 17:08:40 +01:00
Julie Blanc
678698b55d fonts & colors 2025-12-09 14:05:53 +01:00
61 changed files with 1743 additions and 402 deletions

4
package-lock.json generated
View file

@ -1833,6 +1833,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -2011,6 +2012,7 @@
"integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@bufbuild/protobuf": "^2.5.0",
"buffer-builder": "^0.2.0",
@ -2489,6 +2491,7 @@
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@ -2563,6 +2566,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz",
"integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-sfc": "3.5.25",

View file

@ -1,22 +1,59 @@
button {
cursor: pointer;
border: 1px solid var(--color-browngray-300);
color: var(--color-browngray-300);
background-color: var(--color-panel-bg);
border-radius: var(--border-radius);
padding: 0.1rem 0.3rem;
.unit-toggle{
button:not(.spinner-btn) {
cursor: pointer;
border: 1px solid var(--color-interface-400);
color: var(--color-interface-400);
background-color: var(--color-panel-bg);
border-radius: var(--border-radius);
padding: 0.1rem 0.3rem;
height: calc(var(--input-h)*0.75);
&.active {
border: 1px solid #000;
color: #000;
}
&.tab {
&.active {
background-color: #000;
color: #fff;
border: none;
&:not(.active):hover{
background-color: var(--color-interface-100);
}
&.active {
// border: 1px solid var(--color-interface-700);
color: var(--color-interface-050);
background-color: var(--color-interface-500);
cursor: auto;
}
}
}
#editor-panel .tabs{
.tab{
color: var(--color-interface-600);
font-size: 1rem;
font-family: var(--sans-serif);
height: var(--input-h);
padding: 0 1ch;
border: 1px solid currentColor;
border-radius: calc(var(--input-h));
font-weight: 500;
&.active{
background-color: var(--color-interface-400);
border-color: var(--color-interface-400);
color: var(--color-panel-bg);
}
&:not(.active):hover{
cursor: pointer;
background-color: var(--color-interface-100);
}
}
// .tab {
// &.active {
// background-color: var(--color-txt);
// color: #fff;
// border: none;
// }
// }
}

View file

@ -0,0 +1,105 @@
:root{
// BRONZESUBTIL
--color-interface-050: #f5f0ed;
--color-interface-050: #f8f5f4;
--color-interface-100: #efe9e6;
--color-interface-200: #e0d5d0;
--color-interface-300: #cabbb5;
--color-interface-400: #b2a29c;
--color-interface-500: #998985;
--color-interface-600: #7f716e;
--color-interface-700: #675b58;
--color-interface-800: #5b4f4c;
--color-interface-900: #483c39;
--color-050: #f5f0ed;
--color-050: #f8f5f4;
--color-100: #efe9e6;
--color-200: #e0d5d0;
--color-300: #cabbb5;
--color-400: #b2a29c;
--color-500: #998985;
--color-600: #7f716e;
--color-700: #675b58;
--color-800: #5b4f4c;
--color-900: #483c39;
// MARRON
// --color-interface-050: #fcf7f4;
// --color-interface-100: #f5eae4;
// --color-interface-200: #e9d4c8;
// --color-interface-300: #d8b7a6;
// --color-interface-400: #c39986;
// --color-interface-500: #ad826f;
// --color-interface-600: #916b5b;
// --color-interface-700: #755548;
// --color-interface-800: #5c443a;
// --color-interface-900: #46342b;
//CHAMPAGNE
// --color-interface-050: #f9f6f4;
// --color-interface-100: #f2ebe6;
// --color-interface-200: #e6d8cf;
// --color-interface-300: #d4c2b7;
// --color-interface-400: #c2ac9d;
// --color-interface-500: #ac9383;
// --color-interface-600: #927a6a;
// --color-interface-700: #776356;
// --color-interface-800: #5e4e45;
// --color-interface-900: #473a32;
// GREY
// --color-interface-050: #f7f6f4;
// --color-interface-100: #eceae6;
// --color-interface-200: #dedbd6;
// --color-interface-300: #c9c5be;
// --color-interface-400: #b4afa7;
// --color-interface-500: #9d978f;
// --color-interface-600: #86817a;
// --color-interface-700: #6e6a63;
// --color-interface-800: #55534d;
// --color-interface-900: #3e3c37;
}
[data-color-type="elem"]{
--color-050: #fff4ec;
--color-100: #ffe6d6;
--color-200: #ffd1b5;
--color-300: #ffbc94;
--color-400: #ffa673;
--color-500: #ff945b;
--color-600: #ff8d54;
--color-700: #f97f46;
--color-800: #ff8a50;
--color-900: #d96a30;
}
[data-color-type="page"]{
--color-050: #f4f0ff;
--color-100: #e7e1ff;
--color-200: #d4c8ff;
--color-300: #b9a8ff;
--color-400: #9f89ff;
--color-500: #866aff;
--color-600: #7a52ff;
--color-700: #7442ff;
--color-800: #7136ff;
--color-900: #5223d6;
}

View file

@ -0,0 +1,162 @@
// DM Sans - Variable font (recommended)
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans[opsz,wght].woff2') format('woff2-variations');
font-weight: 100 1000;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-Italic[opsz,wght].woff2') format('woff2-variations');
font-weight: 100 1000;
font-style: italic;
font-display: swap;
}
// DM Sans - Static weights (fallback)
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-Thin.woff2') format('woff2');
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-ExtraLight.woff2') format('woff2');
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-ExtraBold.woff2') format('woff2');
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'DM Sans';
src: url('/assets/fonts/DMSans/DMSans-Black.woff2') format('woff2');
font-weight: 900;
font-style: normal;
font-display: swap;
}
// Inconsolata - Monospace font
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-Thin.woff2') format('woff2');
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-ExtraLight.woff2') format('woff2');
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-ExtraBold.woff2') format('woff2');
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inconsolata';
src: url('/assets/fonts/Inconsolata/Inconsolata-Black.woff2') format('woff2');
font-weight: 900;
font-style: normal;
font-display: swap;
}

View file

@ -0,0 +1,56 @@
.settings-section {
margin-top: 3em;
// .cons
h2 {
margin-bottom: var(--space);
font-weight: 600;
font-size: 1.4rem;
// border-radius: var(--border-radius);
// height: var(--input-h);
// padding: 0 1ch;
// display: flex;
// align-items: center;
// color: var(--color-interface-050);
// background-color: var(--color-800);
// display: inline-block;
border-bottom: 1px solid var(--color-200);
color: var(--color-800);
// border-bottom: 2px solid currentColor;
// margin-bottom: 2rem;
}
.infos{
font-size: 0.8rem;
color: var(--color-interface-400);
}
.settings-subsection:not(:last-child) {
border-bottom: 1px solid var(--color-interface-100);
}
.settings-subsection {
padding: var(--space-xs) 0;
h3 {
margin-top: calc(var(--space-xs)*1.5);
margin-bottom: calc(var(--space-xs)*2);
// color: var(--color-600);
font-size: 1rem;
font-weight: 600;
}
}
}

View file

@ -1,17 +1,132 @@
select,
input[type="text"],
input[type="number"] {
background-color: var(--color-browngray-300);
height: var(--input-h);
border: 1px solid var(--color-interface-200);
background-color: var(--color-interface-100);
font-family: var(--sans-serif);
font-size: 1rem;
// min-width: var(--input-w);
// width: 100%;
// padding: 0 1ch;
}
.field {
display: flex;
// label{
// width: var(--label-w);
// background-color: red;
// flex-grow: 3;
// }
label {
font-weight: 600;
color: var(--color-800);
}
.input-with-unit {
display: flex;
gap: 0.3rem;
}
.unit-toggle {
height: var(--input-h);
display: flex;
gap: 0.3rem;
align-items: center;
}
.input-with-color {
width: 100%;
.clr-field {
width: 100%;
display: grid;
grid-template-columns: var(--input-h);
grid-gap: 1ch;
button{
grid-column: 1;
position: relative;
border-radius: var(--border-radius);
}
input{
grid-column: 2;
}
}
}
}
.field{
display: grid;
grid-template-columns: var(--label-w) 1fr;
label{
align-self: center;
}
}
.field-font{
display: grid;
grid-template-columns: var(--label-w) 1fr;
grid-template-rows: var(--input-h) var(--input-h);
select{
width: 100%;
}
.field-checkbox{
padding-top: var(--space-xs);
label{
font-weight: 400;
margin-left: 0.75ch;
}
}
}
.field-margin, .field-size{
display: inline-grid;
width: calc(50% - 1ch);
grid-template-columns: 6.5ch var(--input-w-small) 1fr;
margin-bottom: var(--space-xs);
input{
width: var(--input-w-small);
padding-left: 0.75ch;
}
&:nth-of-type(odd){
margin-right: 2ch;
}
}
.checkbox-field{
margin: calc(var(--space-xs)*2) 0;
grid-template-columns: 3ch 1fr;
label{
// font-weight: normal;
}
input{
justify-self: left;
margin: 0;
}
}
.field--view-only {
opacity: 0.3;
}
/* Label with CSS tooltip */
.label-with-tooltip {
text-decoration: underline dotted;
text-decoration-color: var(--color-browngray-200);
text-decoration: underline dotted 1px var(--color-200);
text-underline-offset: 2px;
cursor: help;
position: relative;
@ -23,9 +138,9 @@ input[type="number"] {
left: 0;
margin-bottom: 4px;
padding: 0.25rem 0.5rem;
background: var(--color-browngray-700);
color: var(--color-browngray-100);
font-family: "Courier New", Courier, monospace;
background: var(--color-interface-700);
color: var(--color-interface-050);
font-family: var(--mono);
font-size: 0.75rem;
border-radius: 4px;
white-space: nowrap;
@ -43,86 +158,127 @@ input[type="number"] {
}
}
.settings-section {
h2 {
border-bottom: 1px solid #000;
margin-bottom: var(--space-xs);
}
.settings-subsection:not(:last-child) {
border-bottom: 1px solid var(--color-browngray-050);
}
.settings-subsection {
padding: var(--space-xs) 0;
h3 {
margin-bottom: var(--space-xs);
}
.field {
display: flex;
label,
select {
width: 50%;
}
input {
padding: 0.1rem 0.1rem 0.1rem 0.3rem;
}
.input-with-unit {
display: flex;
gap: 0.3rem;
// INPUTNUMBER ===============================================
.unit-toggle {
display: flex;
gap: 0.3rem;
}
}
.input-with-color {
width: 50%;
.clr-field {
display: flex;
button {
position: absolute;
transform: none;
height: 1.1rem;
top: auto;
right: auto;
cursor: pointer;
}
input {
padding-left: 2.5rem;
width: 100%;
}
}
}
}
&.margins {
display: flex;
flex-wrap: wrap;
row-gap: var(--space-xs);
h3 {
width: 100%;
}
.field {
width: 50%;
label {
width: 30%;
}
.input-with-unit {
input {
width: 50%;
}
}
}
}
}
// Masquer les spinners natifs partout
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
.number-input{
position: relative;
// padding: 0 1ch!important;
input{
padding-top: 0;
padding-bottom: 0;
}
.spinner-buttons{
height: var(--input-h);
width: var(--input-h);
display: flex;
flex-direction: column;
position: absolute;
right: 0;
top: 0;
button{
height: calc(var(--input-h)*0.6);
cursor: pointer;
padding: 0;
svg path{
fill: var(--color-interface-600);
}
&:hover{
svg path{
fill: var(--color-interface-900);
}
}
}
.spinner-down{
svg{
position: relative;
top: -2px;
}
}
}
}
// Composant NumberInput avec boutons personnalisés
// .number-input {
// position: relative;
// display: flex;
// align-items: center;
// width: 100%;
// position: relative;
// input[type="number"] {
// width: 100%;
// box-sizing: border-box;
// }
// .spinner-buttons {
// background-color: red;
// position: absolute;
// right: 1px;
// top: 1px;
// bottom: 1px;
// display: flex;
// flex-direction: column;
// justify-content: center;
// gap: 1px;
// z-index: 10;
// .spinner-btn {
// display: flex;
// align-items: center;
// justify-content: center;
// width: 20px;
// height: 10px;
// padding: 0;
// margin: 0;
// background-color: var(--color-interface-200);
// border: 1px solid var(--color-interface-300);
// border-radius: 2px;
// cursor: pointer;
// transition: background-color 0.15s ease;
// color: var(--color-interface-700);
// line-height: 0;
// svg {
// width: 8px;
// height: 6px;
// display: block;
// }
// &:hover:not(:disabled) {
// background-color: var(--color-interface-300);
// color: var(--color-interface-900);
// }
// &:active:not(:disabled) {
// background-color: var(--color-interface-400);
// }
// &:disabled {
// opacity: 0.3;
// cursor: not-allowed;
// }
// }
// }
// }

View file

@ -25,3 +25,7 @@ button {
background-color: transparent;
border: none;
}
img {
width: 100%;
}

View file

@ -60,38 +60,38 @@
}
// Label with CSS tooltip
.label-with-tooltip {
text-decoration: underline dotted;
text-underline-offset: 2px;
cursor: help;
position: relative;
// .label-with-tooltip {
// text-decoration: underline dotted;
// text-underline-offset: 2px;
// cursor: help;
// position: relative;
&::after {
content: attr(data-css);
position: absolute;
bottom: 100%;
left: 0;
margin-bottom: 4px;
padding: 0.25rem 0.5rem;
background: var(--color-browngray-700, #3d3d3d);
color: var(--color-browngray-100, #f5f5f5);
font-family: "Courier New", Courier, monospace;
font-size: 0.75rem;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition:
opacity 0.15s ease,
visibility 0.15s ease;
z-index: 10;
}
// &::after {
// content: attr(data-css);
// position: absolute;
// bottom: 100%;
// left: 0;
// margin-bottom: 4px;
// padding: 0.25rem 0.5rem;
// background: var(--color-browngray-700, #3d3d3d);
// color: var(--color-browngray-100, #f5f5f5);
// font-family: "Courier New", Courier, monospace;
// font-size: 0.75rem;
// border-radius: 4px;
// white-space: nowrap;
// opacity: 0;
// visibility: hidden;
// transition:
// opacity 0.15s ease,
// visibility 0.15s ease;
// z-index: 10;
// }
&:hover::after {
opacity: 1;
visibility: visible;
}
}
// &:hover::after {
// opacity: 1;
// visibility: visible;
// }
// }
// Inheritance lock/unlock button
.inheritance-btn {

View file

@ -1,15 +1,10 @@
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
input,
select,
figcaption,
label {
font-family: sans-serif;
body {
font-family: var(--sans-serif);
color: var(--color-txt);
}
// code,
// pre,
// .monospace {
// font-family: 'Inconsolata', monospace;
// }

View file

@ -1,15 +1,37 @@
:root {
--color-panel-bg: #e8e6e5;
--color-browngray-050: #f5f3f0;
--color-browngray-200: #d0c4ba;
--color-browngray-300: #b5a9a1;
--color-txt: var(--color-interface-800);
--color-panel-bg: var(--color-interface-050);
--color-page-highlight: #ff8a50;
--color-purple: #7136ff;
--border-radius: 0.2rem;
--space-xs: 0.5rem;
--space-s: 1rem;
--space: 1.5rem;
--curve: cubic-bezier(0.86, 0, 0.07, 1);
--sans-serif: 'DM Sans', sans-serif;
--mono: 'Inconsolata', monospace;
--input-h: 26px;
--input-w: 160px;
--input-w-small: 45px;
--label-w: 18ch;
font-size: 14px;
--panel-w: 540px;
}

View file

@ -1,3 +1,194 @@
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans[opsz,wght].woff2") format("woff2-variations");
font-weight: 100 1000;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-Italic[opsz,wght].woff2") format("woff2-variations");
font-weight: 100 1000;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-Thin.woff2") format("woff2");
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-ExtraLight.woff2") format("woff2");
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-Light.woff2") format("woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-Medium.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-SemiBold.woff2") format("woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-ExtraBold.woff2") format("woff2");
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "DM Sans";
src: url("/assets/fonts/DMSans/DMSans-Black.woff2") format("woff2");
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-Thin.woff2") format("woff2");
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-ExtraLight.woff2") format("woff2");
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-Light.woff2") format("woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-Medium.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-SemiBold.woff2") format("woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-ExtraBold.woff2") format("woff2");
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inconsolata";
src: url("/assets/fonts/Inconsolata/Inconsolata-Black.woff2") format("woff2");
font-weight: 900;
font-style: normal;
font-display: swap;
}
:root {
--color-interface-050: #f5f0ed;
--color-interface-050: #f8f5f4;
--color-interface-100: #efe9e6;
--color-interface-200: #e0d5d0;
--color-interface-300: #cabbb5;
--color-interface-400: #b2a29c;
--color-interface-500: #998985;
--color-interface-600: #7f716e;
--color-interface-700: #675b58;
--color-interface-800: #5b4f4c;
--color-interface-900: #483c39;
--color-050: #f5f0ed;
--color-050: #f8f5f4;
--color-100: #efe9e6;
--color-200: #e0d5d0;
--color-300: #cabbb5;
--color-400: #b2a29c;
--color-500: #998985;
--color-600: #7f716e;
--color-700: #675b58;
--color-800: #5b4f4c;
--color-900: #483c39;
}
[data-color-type=elem] {
--color-050: #fff4ec;
--color-100: #ffe6d6;
--color-200: #ffd1b5;
--color-300: #ffbc94;
--color-400: #ffa673;
--color-500: #ff945b;
--color-600: #ff8d54;
--color-700: #f97f46;
--color-800: #ff8a50;
--color-900: #d96a30;
}
[data-color-type=page] {
--color-050: #f4f0ff;
--color-100: #e7e1ff;
--color-200: #d4c8ff;
--color-300: #b9a8ff;
--color-400: #9f89ff;
--color-500: #866aff;
--color-600: #7a52ff;
--color-700: #7442ff;
--color-800: #7136ff;
--color-900: #5223d6;
}
body,
html {
padding: 0;
@ -25,32 +216,35 @@ button {
border: none;
}
img {
width: 100%;
}
:root {
--color-panel-bg: #e8e6e5;
--color-browngray-050: #f5f3f0;
--color-browngray-200: #d0c4ba;
--color-browngray-300: #b5a9a1;
--color-txt: var(--color-interface-800);
--color-panel-bg: var(--color-interface-050);
--color-page-highlight: #ff8a50;
--color-purple: #7136ff;
--border-radius: 0.2rem;
--space-xs: 0.5rem;
--space-s: 1rem;
--space: 1.5rem;
--curve: cubic-bezier(0.86, 0, 0.07, 1);
--sans-serif: "DM Sans", sans-serif;
--mono: "Inconsolata", monospace;
--input-h: 26px;
--input-w: 160px;
--input-w-small: 45px;
--label-w: 18ch;
font-size: 14px;
--panel-w: 540px;
}
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
input,
select,
figcaption,
label {
font-family: sans-serif;
body {
font-family: var(--sans-serif);
color: var(--color-txt);
}
/* PagedJS print styles */
@ -75,7 +269,93 @@ h2 {
select,
input[type=text],
input[type=number] {
background-color: var(--color-browngray-300);
height: var(--input-h);
border: 1px solid var(--color-interface-200);
background-color: var(--color-interface-100);
font-family: var(--sans-serif);
font-size: 1rem;
}
.field {
display: flex;
}
.field label {
font-weight: 600;
color: var(--color-800);
}
.field .input-with-unit {
display: flex;
gap: 0.3rem;
}
.field .unit-toggle {
height: var(--input-h);
display: flex;
gap: 0.3rem;
align-items: center;
}
.field .input-with-color {
width: 100%;
}
.field .input-with-color .clr-field {
width: 100%;
display: grid;
grid-template-columns: var(--input-h);
grid-gap: 1ch;
}
.field .input-with-color .clr-field button {
grid-column: 1;
position: relative;
border-radius: var(--border-radius);
}
.field .input-with-color .clr-field input {
grid-column: 2;
}
.field {
display: grid;
grid-template-columns: var(--label-w) 1fr;
}
.field label {
align-self: center;
}
.field-font {
display: grid;
grid-template-columns: var(--label-w) 1fr;
grid-template-rows: var(--input-h) var(--input-h);
}
.field-font select {
width: 100%;
}
.field-font .field-checkbox {
padding-top: var(--space-xs);
}
.field-font .field-checkbox label {
font-weight: 400;
margin-left: 0.75ch;
}
.field-margin, .field-size {
display: inline-grid;
width: calc(50% - 1ch);
grid-template-columns: 6.5ch var(--input-w-small) 1fr;
margin-bottom: var(--space-xs);
}
.field-margin input, .field-size input {
width: var(--input-w-small);
padding-left: 0.75ch;
}
.field-margin:nth-of-type(odd), .field-size:nth-of-type(odd) {
margin-right: 2ch;
}
.checkbox-field {
margin: calc(var(--space-xs) * 2) 0;
grid-template-columns: 3ch 1fr;
}
.checkbox-field input {
justify-self: left;
margin: 0;
}
.field--view-only {
@ -84,9 +364,8 @@ input[type=number] {
/* Label with CSS tooltip */
.label-with-tooltip {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
text-decoration-color: var(--color-browngray-200);
-webkit-text-decoration: underline dotted 1px var(--color-200);
text-decoration: underline dotted 1px var(--color-200);
text-underline-offset: 2px;
cursor: help;
position: relative;
@ -98,9 +377,9 @@ input[type=number] {
left: 0;
margin-bottom: 4px;
padding: 0.25rem 0.5rem;
background: var(--color-browngray-700);
color: var(--color-browngray-100);
font-family: "Courier New", Courier, monospace;
background: var(--color-interface-700);
color: var(--color-interface-050);
font-family: var(--mono);
font-size: 0.75rem;
border-radius: 4px;
white-space: nowrap;
@ -114,89 +393,112 @@ input[type=number] {
visibility: visible;
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
}
.number-input {
position: relative;
}
.number-input input {
padding-top: 0;
padding-bottom: 0;
}
.number-input .spinner-buttons {
height: var(--input-h);
width: var(--input-h);
display: flex;
flex-direction: column;
position: absolute;
right: 0;
top: 0;
}
.number-input .spinner-buttons button {
height: calc(var(--input-h) * 0.6);
cursor: pointer;
padding: 0;
}
.number-input .spinner-buttons button svg path {
fill: var(--color-interface-600);
}
.number-input .spinner-buttons button:hover svg path {
fill: var(--color-interface-900);
}
.number-input .spinner-buttons .spinner-down svg {
position: relative;
top: -2px;
}
.settings-section {
margin-top: 3em;
}
.settings-section h2 {
border-bottom: 1px solid #000;
margin-bottom: var(--space-xs);
margin-bottom: var(--space);
font-weight: 600;
font-size: 1.4rem;
border-bottom: 1px solid var(--color-200);
color: var(--color-800);
}
.settings-section .infos {
font-size: 0.8rem;
color: var(--color-interface-400);
}
.settings-section .settings-subsection:not(:last-child) {
border-bottom: 1px solid var(--color-browngray-050);
border-bottom: 1px solid var(--color-interface-100);
}
.settings-section .settings-subsection {
padding: var(--space-xs) 0;
}
.settings-section .settings-subsection h3 {
margin-bottom: var(--space-xs);
}
.settings-section .settings-subsection .field {
display: flex;
}
.settings-section .settings-subsection .field label,
.settings-section .settings-subsection .field select {
width: 50%;
}
.settings-section .settings-subsection .field input {
padding: 0.1rem 0.1rem 0.1rem 0.3rem;
}
.settings-section .settings-subsection .field .input-with-unit {
display: flex;
gap: 0.3rem;
}
.settings-section .settings-subsection .field .input-with-unit .unit-toggle {
display: flex;
gap: 0.3rem;
}
.settings-section .settings-subsection .field .input-with-color {
width: 50%;
}
.settings-section .settings-subsection .field .input-with-color .clr-field {
display: flex;
}
.settings-section .settings-subsection .field .input-with-color .clr-field button {
position: absolute;
transform: none;
height: 1.1rem;
top: auto;
right: auto;
cursor: pointer;
}
.settings-section .settings-subsection .field .input-with-color .clr-field input {
padding-left: 2.5rem;
width: 100%;
}
.settings-section .settings-subsection.margins {
display: flex;
flex-wrap: wrap;
row-gap: var(--space-xs);
}
.settings-section .settings-subsection.margins h3 {
width: 100%;
}
.settings-section .settings-subsection.margins .field {
width: 50%;
}
.settings-section .settings-subsection.margins .field label {
width: 30%;
}
.settings-section .settings-subsection.margins .field .input-with-unit input {
width: 50%;
margin-top: calc(var(--space-xs) * 1.5);
margin-bottom: calc(var(--space-xs) * 2);
font-size: 1rem;
font-weight: 600;
}
button {
.unit-toggle button:not(.spinner-btn) {
cursor: pointer;
border: 1px solid var(--color-browngray-300);
color: var(--color-browngray-300);
border: 1px solid var(--color-interface-400);
color: var(--color-interface-400);
background-color: var(--color-panel-bg);
border-radius: var(--border-radius);
padding: 0.1rem 0.3rem;
height: calc(var(--input-h) * 0.75);
}
button.active {
border: 1px solid #000;
color: #000;
.unit-toggle button:not(.spinner-btn):not(.active):hover {
background-color: var(--color-interface-100);
}
button.tab.active {
background-color: #000;
color: #fff;
border: none;
.unit-toggle button:not(.spinner-btn).active {
color: var(--color-interface-050);
background-color: var(--color-interface-500);
cursor: auto;
}
#editor-panel .tabs .tab {
color: var(--color-interface-600);
font-size: 1rem;
font-family: var(--sans-serif);
height: var(--input-h);
padding: 0 1ch;
border: 1px solid currentColor;
border-radius: calc(var(--input-h));
font-weight: 500;
}
#editor-panel .tabs .tab.active {
background-color: var(--color-interface-400);
border-color: var(--color-interface-400);
color: var(--color-panel-bg);
}
#editor-panel .tabs .tab:not(.active):hover {
cursor: pointer;
background-color: var(--color-interface-100);
}
.settings-popup {
@ -258,36 +560,6 @@ button.tab.active {
font-weight: 600;
}
.label-with-tooltip {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
text-underline-offset: 2px;
cursor: help;
position: relative;
}
.label-with-tooltip::after {
content: attr(data-css);
position: absolute;
bottom: 100%;
left: 0;
margin-bottom: 4px;
padding: 0.25rem 0.5rem;
background: var(--color-browngray-700, #3d3d3d);
color: var(--color-browngray-100, #f5f5f5);
font-family: "Courier New", Courier, monospace;
font-size: 0.75rem;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: opacity 0.15s ease, visibility 0.15s ease;
z-index: 10;
}
.label-with-tooltip:hover::after {
opacity: 1;
visibility: visible;
}
.inheritance-btn {
display: flex;
align-items: center;

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,10 @@
@import "src/_reset.scss";
@import "src/_variables.scss";
@import "src/_text.scss";
@import "src/_print-styles.scss";
@import "src/_forms.scss";
@import "src/_buttons.scss";
@import "src/_settings-popup.scss";
@use "src/_fonts.scss" as *;
@use "src/_colors.scss" as *;
@use "src/_reset.scss" as *;
@use "src/_variables.scss" as *;
@use "src/_text.scss" as *;
@use "src/_print-styles.scss" as *;
@use "src/_forms.scss" as *;
@use "src/_forms-section.scss" as *;
@use "src/_buttons.scss" as *;
@use "src/_settings-popup.scss" as *;

View file

@ -24,5 +24,13 @@ h2 {
p {
font-size: 1rem;
margin: 0mm 0mm 24mm 0mm;
margin: 0mm 0mm 10mm 0mm;
}
img {
width: 100%;
}
li p {
margin-bottom: 0;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,137 @@
/* DMSans Font Faces */
@font-face {
font-family: "DMSans";
src: url("DMSans-Black.woff2") format("woff2");
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-BlackItalic.woff2") format("woff2");
font-weight: 900;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-BoldItalic.woff2") format("woff2");
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-ExtraBlack.woff2") format("woff2");
font-weight: 950;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-ExtraBlackItalic.woff2") format("woff2");
font-weight: 950;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-ExtraBold.woff2") format("woff2");
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-ExtraBoldItalic.woff2") format("woff2");
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-ExtraLight.woff2") format("woff2");
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-ExtraLightItalic.woff2") format("woff2");
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-Italic.woff2") format("woff2");
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-Italic[opsz,wght].woff2") format("woff2");
font-weight: 100 900;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-Light.woff2") format("woff2");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-LightItalic.woff2") format("woff2");
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-Medium.woff2") format("woff2");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-MediumItalic.woff2") format("woff2");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-SemiBold.woff2") format("woff2");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-SemiBoldItalic.woff2") format("woff2");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-Thin.woff2") format("woff2");
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: "DMSans";
src: url("DMSans-ThinItalic.woff2") format("woff2");
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: "DMSans";
src: url("DMSans[opsz,wght].woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
}
/* Example usage */
body {
font-family: "DMSans", sans-serif;
}/*# sourceMappingURL=stylesheet.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sources":["stylesheet.scss","stylesheet.css"],"names":[],"mappings":"AAAA,sBAAA;AACA;EACE,qBAAA;EACA,8CAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,oDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,6CAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,mDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,mDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,yDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,kDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,wDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,mDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,yDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,+CAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,0DAAA;EACA,oBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,8CAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,oDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,+CAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,qDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,gDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,iDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,uDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,6CAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,mDAAA;EACA,gBAAA;EACA,kBAAA;ACCF;ADCA;EACE,qBAAA;EACA,mDAAA;EACA,oBAAA;EACA,kBAAA;ACCF;ADEA,kBAAA;AACA;EACE,iCAAA;ACAF","file":"stylesheet.css"}

View file

@ -0,0 +1,138 @@
/* DMSans Font Faces */
@font-face {
font-family: 'DMSans';
src: url('DMSans-Black.woff2') format('woff2');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-BlackItalic.woff2') format('woff2');
font-weight: 900;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-BoldItalic.woff2') format('woff2');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-ExtraBlack.woff2') format('woff2');
font-weight: 950;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-ExtraBlackItalic.woff2') format('woff2');
font-weight: 950;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-ExtraBold.woff2') format('woff2');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-ExtraBoldItalic.woff2') format('woff2');
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-ExtraLight.woff2') format('woff2');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-ExtraLightItalic.woff2') format('woff2');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-Italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-Italic[opsz,wght].woff2') format('woff2');
font-weight: 100 900;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-LightItalic.woff2') format('woff2');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-MediumItalic.woff2') format('woff2');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-SemiBoldItalic.woff2') format('woff2');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-Thin.woff2') format('woff2');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans-ThinItalic.woff2') format('woff2');
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: 'DMSans';
src: url('DMSans[opsz,wght].woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
}
/* Example usage */
body {
font-family: 'DMSans', sans-serif;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,61 @@
/* Inconsolata Font Faces (ordered from lightest to boldest) */
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-Thin.woff2') format('woff2');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-ExtraLight.woff2') format('woff2');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-ExtraBold.woff2') format('woff2');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: 'Inconsolata';
src: url('Inconsolata-Black.woff2') format('woff2');
font-weight: 900;
font-style: normal;
}
/* Example usage */
/* code, pre, .mono {
font-family: 'Inconsolata', monospace;
} */

View file

@ -8,7 +8,7 @@
<?= e($page->isHomePage() != true, $page->title() . ' - ') . $site->title() ?>
</title>
<link rel="stylesheet" href="<?= url('assets/css/style..css') ?>">
<link rel="stylesheet" href="<?= url('assets/css/style.css') ?>">
<link rel="stylesheet" href="<?= url('assets/css/pagedjs-interface.css') ?>">
<!-- À SUPPRIMER EN PRODUCTION -->

View file

@ -58,10 +58,10 @@
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
<label class="label-with-tooltip" data-css="font-size">Taille du texte</label>
<div class="input-with-unit">
<input
type="number"
v-model.number="fontSize.value"
min="0"
<NumberInput
v-model="fontSize.value"
:min="0"
:step="1"
:disabled="inheritanceLocked"
/>
<div class="unit-toggle">
@ -109,7 +109,7 @@
<!-- Color -->
<div class="settings-subsection">
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
<div class="field field-simple" :class="{ 'field--view-only': inheritanceLocked }">
<label class="label-with-tooltip" data-css="color">Couleur</label>
<div class="input-with-color">
<input
@ -125,7 +125,7 @@
<!-- Background -->
<div class="settings-subsection">
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
<div class="field field-simple" :class="{ 'field--view-only': inheritanceLocked }">
<label class="label-with-tooltip" data-css="background">Arrière-plan</label>
<div class="input-with-color">
<input
@ -144,10 +144,10 @@
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
<label class="label-with-tooltip" data-css="margin">Marges extérieures</label>
<div class="input-with-unit">
<input
type="number"
v-model.number="marginOuter.value"
min="0"
<NumberInput
v-model="marginOuter.value"
:min="0"
:step="1"
:disabled="inheritanceLocked"
/>
<div class="unit-toggle">
@ -177,10 +177,10 @@
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
<label class="label-with-tooltip" data-css="padding">Marges intérieures</label>
<div class="input-with-unit">
<input
type="number"
v-model.number="paddingInner.value"
min="0"
<NumberInput
v-model="paddingInner.value"
:min="0"
:step="1"
:disabled="inheritanceLocked"
/>
<div class="unit-toggle">
@ -274,6 +274,8 @@
import { ref, computed, watch } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
import hljs from 'highlight.js/lib/core';
@ -306,7 +308,7 @@ const colorInput = ref(null);
const backgroundInput = ref(null);
let isUpdatingFromStore = false;
let updateTimer = null;
const { debouncedUpdate } = useDebounce(500);
// Style properties
const fontFamily = ref({ value: 'Alegreya Sans' });
@ -319,11 +321,6 @@ const background = ref({ value: 'transparent' });
const marginOuter = ref({ value: 0, unit: 'mm' });
const paddingInner = ref({ value: 0, unit: 'mm' });
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
@ -372,6 +369,22 @@ const elementCss = computed(() => {
return stylesheetStore.extractBlock(selector.value) || '';
});
// Remove the element-specific CSS block to restore inheritance
const removeElementBlock = () => {
if (!selector.value) return;
const block = stylesheetStore.extractBlock(selector.value);
if (block) {
// Escape special regex characters in selector
const escaped = selector.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Remove the block and any surrounding whitespace
stylesheetStore.content = stylesheetStore.content.replace(
new RegExp(`\\n?${escaped}\\s*\\{[^}]*\\}\\n?`),
'\n'
);
}
};
const highlightedCss = computed(() => {
if (!elementCss.value) return '';
return hljs.highlight(elementCss.value, { language: 'css' }).value;
@ -604,6 +617,9 @@ const open = (element, event, count = null) => {
// Store instance count if provided, otherwise calculate it
elementInstanceCount.value = count !== null ? count : getInstanceCount(selector.value);
// Read inheritance state from element's data attribute
inheritanceLocked.value = element.dataset.inheritanceUnlocked !== 'true';
// Load values from stylesheet
loadValuesFromStylesheet();
@ -667,8 +683,22 @@ const handleIframeClick = (event, targetElement = null, elementCount = null) =>
};
const toggleInheritance = () => {
const wasLocked = inheritanceLocked.value;
inheritanceLocked.value = !inheritanceLocked.value;
// TODO: Implement CSS priority logic when unlocked
// Store the inheritance state in the element's data attribute
if (selectedElement.value) {
if (inheritanceLocked.value) {
delete selectedElement.value.dataset.inheritanceUnlocked;
} else {
selectedElement.value.dataset.inheritanceUnlocked = 'true';
}
}
// When re-locking, remove the element-specific CSS block to restore inheritance
if (inheritanceLocked.value && !wasLocked) {
removeElementBlock();
}
};
defineExpose({ handleIframeClick, close, visible });

View file

@ -27,10 +27,10 @@
>
<label class="label-with-tooltip" data-css="margin-top">Haut</label>
<div class="input-with-unit">
<input
type="number"
v-model.number="margins.top.value"
min="0"
<NumberInput
v-model="margins.top.value"
:min="0"
:step="1"
:disabled="inheritanceLocked"
/>
<div class="unit-toggle">
@ -68,10 +68,10 @@
>
<label class="label-with-tooltip" data-css="margin-bottom">Bas</label>
<div class="input-with-unit">
<input
type="number"
v-model.number="margins.bottom.value"
min="0"
<NumberInput
v-model="margins.bottom.value"
:min="0"
:step="1"
:disabled="inheritanceLocked"
/>
<div class="unit-toggle">
@ -109,10 +109,10 @@
>
<label class="label-with-tooltip" data-css="margin-left">Gauche</label>
<div class="input-with-unit">
<input
type="number"
v-model.number="margins.left.value"
min="0"
<NumberInput
v-model="margins.left.value"
:min="0"
:step="1"
:disabled="inheritanceLocked"
/>
<div class="unit-toggle">
@ -150,10 +150,10 @@
>
<label class="label-with-tooltip" data-css="margin-right">Droite</label>
<div class="input-with-unit">
<input
type="number"
v-model.number="margins.right.value"
min="0"
<NumberInput
v-model="margins.right.value"
:min="0"
:step="1"
:disabled="inheritanceLocked"
/>
<div class="unit-toggle">
@ -285,6 +285,8 @@
import { ref, computed, watch, onMounted } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
import hljs from 'highlight.js/lib/core';
@ -311,7 +313,7 @@ const inheritanceLocked = ref(true);
const backgroundColorInput = ref(null);
let isUpdatingFromStore = false;
let updateTimer = null;
const { debouncedUpdate } = useDebounce(500);
const margins = ref({
top: { value: 0, unit: 'mm' },
@ -327,11 +329,6 @@ const background = ref({
const pattern = ref('');
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
@ -535,8 +532,8 @@ const open = (pageElement, event, count = 1) => {
// Extract template name from data-page-type attribute
templateName.value = pageElement.getAttribute('data-page-type') || '';
// Reset inheritance state when opening
inheritanceLocked.value = true;
// Read inheritance state from page element's data attribute
inheritanceLocked.value = pageElement.dataset.inheritanceUnlocked !== 'true';
// Load values from stylesheet (@page block)
loadValuesFromStylesheet();
@ -586,6 +583,15 @@ const toggleInheritance = () => {
const wasLocked = inheritanceLocked.value;
inheritanceLocked.value = !inheritanceLocked.value;
// Store the inheritance state in the page element's data attribute
if (selectedPageElement.value) {
if (inheritanceLocked.value) {
delete selectedPageElement.value.dataset.inheritanceUnlocked;
} else {
selectedPageElement.value.dataset.inheritanceUnlocked = 'true';
}
}
if (inheritanceLocked.value && !wasLocked) {
// Re-locking: remove the template-specific block
// Fields keep their values, but preview returns to @page defaults

View file

@ -70,7 +70,7 @@ const activeTab = inject('activeTab');
position: fixed;
top: 0;
left: 0;
width: 35rem;
width: var(--panel-w);
height: 100vh;
display: flex;
flex-direction: column;
@ -79,7 +79,7 @@ const activeTab = inject('activeTab');
nav {
position: absolute;
margin: 1rem 0 0 1rem;
margin: 1.5rem 0 0 2rem;
display: flex;
gap: 0.5rem;
@ -88,7 +88,7 @@ nav {
.close-button {
position: absolute;
top: 1rem;
top: 1.5rem;
right: 1rem;
z-index: 2;
@ -100,7 +100,7 @@ nav {
border: none;
cursor: pointer;
color: var(--color-browngray-300);
color: var(--color-interface-300);
transition: color 0.2s ease;
&:hover {
@ -117,9 +117,9 @@ nav {
flex: 1;
overflow: hidden;
position: relative;
left: -35rem;
left: calc(var(--panel-w)*-1);
padding: 4rem 1rem 1rem 1rem;
padding: 4rem 0;
background-color: var(--color-panel-bg);
box-shadow: -5px 0px 12px;
@ -134,5 +134,8 @@ nav {
.tab-panel {
height: 100%;
overflow-y: auto;
overflow-x: hidden;
padding: 0 2em;
// padding-left: 1em;
}
</style>

View file

@ -1,9 +1,11 @@
<template>
<section class="settings-section">
<section class="settings-section" id="settings-section_page" data-color-type="page">
<h2>Réglage des pages</h2>
<div class="container">
<div class="settings-subsection">
<div class="field">
<div class="field field-simple">
<label for="page-format" class="label-with-tooltip" data-css="size"
>Format d'impression</label
>
@ -18,7 +20,7 @@
</div>
<div class="settings-subsection">
<div class="field field--view-only">
<div class="field field-size field--view-only">
<label for="page-width" class="label-with-tooltip" data-css="width"
>Largeur</label
>
@ -31,7 +33,7 @@
<button type="button" disabled>mm</button>
</div>
<div class="field field--view-only">
<div class="field field-size field--view-only">
<label for="page-height" class="label-with-tooltip" data-css="height"
>Hauteur</label
>
@ -48,16 +50,16 @@
<div class="settings-subsection margins">
<h3>Marges</h3>
<div class="field">
<div class="field field-margin">
<label for="margin-top" class="label-with-tooltip" data-css="margin-top"
>Haut</label
>
<div class="input-with-unit">
<input
<NumberInput
id="margin-top"
type="number"
v-model.number="margins.top.value"
min="0"
v-model="margins.top.value"
:min="0"
:step="1"
/>
<div class="unit-toggle">
<button
@ -85,7 +87,7 @@
</div>
</div>
<div class="field">
<div class="field field-margin">
<label
for="margin-bottom"
class="label-with-tooltip"
@ -93,11 +95,11 @@
>Bas</label
>
<div class="input-with-unit">
<input
<NumberInput
id="margin-bottom"
type="number"
v-model.number="margins.bottom.value"
min="0"
v-model="margins.bottom.value"
:min="0"
:step="1"
/>
<div class="unit-toggle">
<button
@ -125,7 +127,7 @@
</div>
</div>
<div class="field">
<div class="field field-margin">
<label
for="margin-left"
class="label-with-tooltip"
@ -133,11 +135,11 @@
>Gauche</label
>
<div class="input-with-unit">
<input
<NumberInput
id="margin-left"
type="number"
v-model.number="margins.left.value"
min="0"
v-model="margins.left.value"
:min="0"
:step="1"
/>
<div class="unit-toggle">
<button
@ -165,7 +167,7 @@
</div>
</div>
<div class="field">
<div class="field field-margin">
<label
for="margin-right"
class="label-with-tooltip"
@ -173,11 +175,11 @@
>Droite</label
>
<div class="input-with-unit">
<input
<NumberInput
id="margin-right"
type="number"
v-model.number="margins.right.value"
min="0"
v-model="margins.right.value"
:min="0"
:step="1"
/>
<div class="unit-toggle">
<button
@ -207,7 +209,7 @@
</div>
<div class="settings-subsection">
<div class="field">
<div class="field field-simple">
<label for="background" class="label-with-tooltip" data-css="background"
>Arrière-plan</label
>
@ -242,7 +244,7 @@
</div>
<div class="settings-subsection">
<div class="field">
<div class="field field-simple">
<label
for="pattern"
class="label-with-tooltip"
@ -279,21 +281,24 @@
>
</div>
</div>
</div>
</section>
</template>
<script setup>
import { ref, computed, watch, onMounted, inject } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet';
import { useDebounce } from '../../composables/useDebounce';
import Coloris from '@melloware/coloris';
import NumberInput from '../ui/NumberInput.vue';
import '@melloware/coloris/dist/coloris.css';
const stylesheetStore = useStylesheetStore();
const { debouncedUpdate } = useDebounce(500);
const backgroundColorInput = ref(null);
const activeTab = inject('activeTab', ref('document'));
let isUpdatingFromStore = false;
let updateTimer = null;
const pageFormat = ref('A4');
@ -324,11 +329,6 @@ const pattern = ref('');
const pageNumbers = ref(false);
const runningTitle = ref(false);
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};

View file

@ -1,6 +1,8 @@
<template>
<section class="settings-section">
<section class="settings-section" id="settings-section_elem" data-color-type="elem">
<h2>Réglage du texte</h2>
<div class="container">
<p class="infos">
Ces réglages s'appliquent à l'ensemble des éléments du document. Vous
pouvez modifier ensuite les éléments indépendamment.
@ -8,7 +10,7 @@
<!-- Police -->
<div class="settings-subsection">
<div class="field">
<div class="field field-font">
<label for="text-font" class="label-with-tooltip" data-css="font-family">Police</label>
<div class="field-with-option">
<select id="text-font" v-model="font">
@ -46,7 +48,7 @@
<!-- Alignement -->
<div class="settings-subsection">
<div class="field">
<div class="field field-simple">
<label for="text-alignment" class="label-with-tooltip" data-css="text-align">Alignement</label>
<select id="text-alignment" v-model="alignment">
<option v-for="a in alignments" :key="a.value" :value="a.value">
@ -58,7 +60,7 @@
<!-- Couleurs -->
<div class="settings-subsection">
<div class="field">
<div class="field field-simple">
<label for="text-color" class="label-with-tooltip" data-css="color">Couleur</label>
<div class="input-with-color">
<input
@ -70,7 +72,11 @@
/>
</div>
</div>
<div class="field">
</div>
<!-- Couleurs // arrière plan -->
<div class="settings-subsection">
<div class="field field-simple">
<label for="text-background" class="label-with-tooltip" data-css="background">Arrière-plan</label>
<div class="input-with-color">
<input
@ -111,6 +117,8 @@
@change="handleMarginInnerChange"
/>
</div>
</div>
</section>
</template>
@ -122,9 +130,11 @@ import InputWithUnit from '../ui/InputWithUnit.vue';
import MarginEditor from '../ui/MarginEditor.vue';
import { useCssUpdater } from '../../composables/useCssUpdater';
import { useCssSync } from '../../composables/useCssSync';
import { useDebounce } from '../../composables/useDebounce';
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
const { debouncedUpdate } = useDebounce(500);
// Constants
const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman'];
@ -195,26 +205,32 @@ watch(weight, (val) => {
watch(fontSize, (val) => {
if (isUpdatingFromStore) return;
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
debouncedUpdate(() => {
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
});
}, { deep: true });
// Margin/Padding handlers
const handleMarginOuterChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
if (type === 'simple') {
setMargin('p', simple.value, simple.unit);
} else {
setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
debouncedUpdate(() => {
if (type === 'simple') {
setMargin('p', simple.value, simple.unit);
} else {
setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
};
const handleMarginInnerChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
if (type === 'simple') {
setPadding('p', simple.value, simple.unit);
} else {
setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
debouncedUpdate(() => {
if (type === 'simple') {
setPadding('p', simple.value, simple.unit);
} else {
setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
};
// Sync from store

View file

@ -9,14 +9,13 @@
:step="step"
@input="updateValue(Number($event.target.value))"
/>
<input
type="number"
:value="modelValue.value"
<NumberInput
:modelValue="modelValue.value"
:min="min"
:max="max"
:step="step"
class="size-input"
@input="updateValue(Number($event.target.value))"
inputClass="size-input"
@update:modelValue="updateValue"
/>
<UnitToggle
:modelValue="modelValue.unit"
@ -28,6 +27,7 @@
</template>
<script setup>
import NumberInput from './NumberInput.vue';
import UnitToggle from './UnitToggle.vue';
const props = defineProps({

View file

@ -1,14 +1,14 @@
<template>
<div class="margin-editor">
<div class="field">
<div class="field field-margin-all">
<label :for="id" class="label-with-tooltip" :data-css="cssProperty">{{ label }}</label>
<div class="input-with-unit">
<input
<NumberInput
:id="id"
type="number"
:value="simple.value"
min="0"
@input="updateSimpleValue(Number($event.target.value))"
:modelValue="simple.value"
:min="0"
:step="1"
@update:modelValue="updateSimpleValue"
/>
<UnitToggle
:modelValue="simple.unit"
@ -28,15 +28,15 @@
</div>
<div v-if="expanded" class="subsection collapsed-section">
<div v-for="side in sides" :key="side.key" class="field">
<div v-for="side in sides" :key="side.key" class="field field-margin">
<label :for="`${id}-${side.key}`" class="label-with-tooltip" :data-css="`${cssProperty}-${side.key}`">{{ side.label }}</label>
<div class="input-with-unit">
<input
<NumberInput
:id="`${id}-${side.key}`"
type="number"
:value="detailed[side.key].value"
min="0"
@input="updateDetailedValue(side.key, Number($event.target.value))"
:modelValue="detailed[side.key].value"
:min="0"
:step="1"
@update:modelValue="(value) => updateDetailedValue(side.key, value)"
/>
<UnitToggle
:modelValue="detailed[side.key].unit"
@ -51,6 +51,7 @@
<script setup>
import { ref, watch } from 'vue';
import NumberInput from './NumberInput.vue';
import UnitToggle from './UnitToggle.vue';
const props = defineProps({

View file

@ -0,0 +1,93 @@
<template>
<div class="number-input">
<input
type="number"
:value="modelValue"
:min="min"
:max="max"
:step="step"
:id="id"
:class="inputClass"
:disabled="disabled"
@input="handleInput"
/>
<div class="spinner-buttons">
<button
type="button"
class="spinner-btn spinner-up"
@click="increment"
:disabled="disabled || (max !== undefined && modelValue >= max)"
tabindex="-1"
>
<svg width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 0L7.4641 6H0.535898L4 0Z" fill="currentColor"/>
</svg>
</button>
<button
type="button"
class="spinner-btn spinner-down"
@click="decrement"
:disabled="disabled || (min !== undefined && modelValue <= min)"
tabindex="-1"
>
<svg width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 6L0.535898 0H7.4641L4 6Z" fill="currentColor"/>
</svg>
</button>
</div>
</div>
</template>
<script setup>
const props = defineProps({
modelValue: {
type: Number,
required: true
},
min: {
type: Number,
default: undefined
},
max: {
type: Number,
default: undefined
},
step: {
type: Number,
default: 1
},
id: {
type: String,
default: undefined
},
inputClass: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const handleInput = (event) => {
const value = Number(event.target.value);
emit('update:modelValue', value);
};
const increment = () => {
const newValue = props.modelValue + props.step;
if (props.max === undefined || newValue <= props.max) {
emit('update:modelValue', newValue);
}
};
const decrement = () => {
const newValue = props.modelValue - props.step;
if (props.min === undefined || newValue >= props.min) {
emit('update:modelValue', newValue);
}
};
</script>

View file

@ -0,0 +1,15 @@
/**
* Composable for debounced updates
* @param {number} delay - Debounce delay in milliseconds (default: 500ms)
* @returns {Function} debouncedUpdate function
*/
export function useDebounce(delay = 500) {
let timer = null;
const debouncedUpdate = (callback) => {
clearTimeout(timer);
timer = setTimeout(callback, delay);
};
return { debouncedUpdate };
}

View file

@ -1 +1 @@
@import url('/assets/css/style.css');
@import '../public/assets/css/style.scss';

View file

@ -16,20 +16,32 @@ const extractCssValue = (css, selector, property) => {
const updateCssValue = ({ css, selector, property, value, unit }) => {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const formattedValue = unit === '' || unit === undefined ? value : `${value}${unit}`;
if (unit === '') {
const regex = new RegExp(
`(${escaped}\\s*{[^}]*${property}:\\s*)[^;]+`,
'gi'
);
return css.replace(regex, `$1${value}`);
// Check if selector exists
const selectorRegex = new RegExp(`${escaped}\\s*{([^}]*)}`, 'i');
const selectorMatch = css.match(selectorRegex);
if (!selectorMatch) {
// Selector doesn't exist, create it
return css + `\n${selector} {\n ${property}: ${formattedValue};\n}\n`;
}
const regex = new RegExp(
`(${escaped}\\s*{[^}]*${property}:\\s*)[\\d.]+(px|rem|em|mm|cm|in)`,
'gi'
);
return css.replace(regex, `$1${value}${unit}`);
const blockContent = selectorMatch[1];
// Check if property exists in the block
const propertyRegex = new RegExp(`(${property}\\s*:\\s*)([^;]+)(;?)`, 'i');
const propertyMatch = blockContent.match(propertyRegex);
if (!propertyMatch) {
// Property doesn't exist, add it to the block
const newBlockContent = blockContent.trimEnd() + `\n ${property}: ${formattedValue};`;
return css.replace(selectorRegex, `${selector} {${newBlockContent}}`);
}
// Property exists, replace the entire value
const newBlockContent = blockContent.replace(propertyRegex, `$1${formattedValue}$3`);
return css.replace(selectorRegex, `${selector} {${newBlockContent}}`);
};
const cssParsingUtils = { extractCssBlock, extractCssValue, updateCssValue };