1:- module(grammar, [tailwind//1, prefixes//2]).

Tailwind CSS grammar

DCGs for parsing Tailwind selectors to the corresponding CSS

author
- James Cash */
    9:- use_module(library(apply_macros)).   10:- use_module(library(apply), [maplist/3]).   11:- use_module(library(dcg/basics), [integer//1, string_without//2]).   12:- use_module(library(dcg/high_order), [optional//2]).   13:- use_module(library(yall)).   14
   15:- use_module(tw_utils).   16:- use_module(colours, [colour//1,
   17                        has_alpha/1,
   18                        colour_css/2,
   19                        as_transparent/2,
   20                        colour_with_alpha/3]).
 prefixes(-MediaQueries, -StateVariants)// is det
DCG to parse possibly-empty media query & state variant prefixes of a tailwind selector; e.g. in "lg:hover:text-blue" would have MediaQueries be [min_width("1024px")] and StateVariants be [inner(hover)].
   28prefixes(Medias, States) -->
   29    media_queries(Medias), state_variants(States).
   30
   31media_queries([Media|Medias]) -->
   32    media_query(Media), ":", !,
   33    media_queries(Medias).
   34media_queries([]) --> [].
   35
   36media_query(min_width("640px")) --> "sm".
   37media_query(min_width("768px")) --> "md".
   38media_query(min_width("1024px")) --> "lg".
   39media_query(min_width("1280px")) --> "xl".
   40media_query(min_width("1536")) --> "2xl".
   41
   42media_query(color_scheme(light)) --> "light".
   43media_query(color_scheme(dark)) --> "dark".
   44
   45media_query(motion("no-preference")) --> "motion-safe".
   46media_query(motion("reduced")) --> "motion-reduce".
   47
   48state_variants([State|States]) -->
   49    state_variant(State), ":", !,
   50    state_variants(States).
   51state_variants([]) --> [].
   52
   53state_variant(outer(Variant)) -->
   54    "group-", one_of(["hover", "focus", "disabled", "active"],
   55                     GroupVariant), !,
   56    { format(atom(Variant), ".group:~w", [GroupVariant]) }.
   57state_variant(outer(Variant)) -->
   58    "group-attr-", string_without(":", Attr), !,
   59    { format(atom(Variant), ".group[~s]", [Attr]) }.
   60state_variant(inner(Variant)) -->
   61    one_of(
   62        ["hover", "focus", "disabled", "active",
   63         "focus-within", "focus-visible",
   64         "any-link", "link", "visited", "target",
   65         "blank", "required", "optionaly", "valid", "invalid",
   66         "placeholder-shown", "checked", "read-only", "read-write",
   67         % need to make sure prefixes are last, since one_of is greedy
   68         "first-of-type", "last-of-type", "root", "empty"],
   69        Variant).
   70state_variant(inner("first-child")) --> "first", !.
   71state_variant(inner("last-child")) --> "last", !.
   72state_variant(inner("nth-child(odd)")) --> "odd", !.
   73state_variant(inner("nth-child(even)")) --> "even", !.
   74
   75:- discontiguous tailwind//1.   76
   77% Flex
 tailwind(Styles)// is semidet
DCG to parse the many possible Tailwind selectors.
   82tailwind('flex-grow'(V)) -->
   83    "flex-grow-", optional(fraction(N), num(N)),
   84    { value_unit_css(N, V, _{}) }.
   85tailwind('flex-grow'(1)) --> "flex-grow".
   86
   87tailwind('flex-shrink'(V)) -->
   88    "flex-shrink-", optional(fraction(N), num(N)),
   89    { value_unit_css(N, V, _{}) }.
   90tailwind('flex-shrink'(1)) --> "flex-shrink".
   91
   92flex_basis_value(Val) --> fraction(Val).
   93flex_basis_value(Val) --> percentage(Val).
   94flex_basis_value(Val) --> length(Val).
   95flex_basis_value(Val) --> length_unit(Val).
   96flex_basis_value(Val) --> num(Val).
   97flex_basis_value(full_100) --> "full".
   98flex_basis_value(auto) --> "auto".
   99
  100tailwind('flex-basis'(N)) -->
  101    "flex-basis-", flex_basis_value(Val), !,
  102    { value_unit_css(Val, N, _{zero: "",
  103                              number: _{unit: rem,
  104                                        value_fn: div_4},
  105                              fraction: _{unit: '%',
  106                                          value_fn: mul_100}}) }.
  107tailwind('flex-basis'(1)) --> "flex-basis", !.
  108
  109tailwind('flex'("none")) --> "flex-none", !.
  110tailwind('flex'("0 1 auto")) --> "flex-initial", !.
  111tailwind('flex'("1 1 auto")) --> "flex-auto", !.
  112tailwind('flex'(S)) -->
  113    "flex-",
  114    optional(fraction(GrowN), num(GrowN)),
  115    "-", optional(fraction(ShrinkN), num(ShrinkN)),
  116    "-", flex_basis_value(BasisN), !,
  117    { value_unit_css(GrowN, Grow, _{}),
  118      value_unit_css(ShrinkN, Shrink, _{}),
  119      value_unit_css(BasisN, Basis, _{zero_unit: "",
  120                                      number: _{unit: "rem",
  121                                                value_fn: div_4},
  122                                      fraction: _{unit: "%",
  123                                                  value_fn: mul_100}
  124                                     }),
  125      format(string(S), "~w ~w ~w", [Grow, Shrink, Basis]) }.
  126tailwind('flex'(S)) -->
  127    "flex-",
  128    optional(fraction(GrowN), num(GrowN)),
  129    "-", flex_basis_value(ShrinkOrBasisN), !,
  130    { value_unit_css(GrowN, Grow, _{}),
  131      value_unit_css(ShrinkOrBasisN, ShrinkOrBasis, _{}),
  132      format(string(S), "~w ~w", [Grow, ShrinkOrBasis]) }.
  133tailwind('flex'(S)) -->
  134    "flex-", optional(fraction(N), num(N)), !,
  135    { value_unit_css(N, CssN, _{}),
  136      format(string(S), "~w ~w 0%", [CssN, CssN]) }, !.
  137
  138tailwind('flex-direction'("row-reverse")) --> "flex-row-reverse", !.
  139tailwind('flex-direction'("row")) --> "flex-row", !.
  140tailwind('flex-direction'("column-reverse")) --> "flex-col-reverse", !.
  141tailwind('flex-direction'("column")) --> "flex-col", !.
  142
  143tailwind('flex-wrap'(Wrap)) -->
  144    one_of(["wrap-reverse", "wrap", "nowrap"], Wrap).
  145
  146tailwind(order(-9999)) --> "order-first", !.
  147tailwind(order(9999)) --> "order-last", !.
  148tailwind(order(0)) --> "order-none", !.
  149tailwind(order(O)) -->
  150    soft_optional(signus(S), { S = '+' }),
  151    "order-",
  152    integer(N), !,
  153    { value_unit_css(integer(N), O, _{signus: S }) }.
  154
  155% Accessibility
  156
  157tailwind([position(absolute), width("1px"), height("1px"),
  158          padding(0), margin("-1px"), overflow(hidden),
  159          clip("rect(0,0,0,0)"), 'white-space'(nowrap),
  160          'border-width'(0)]) -->
  161    "sr-only", !.
  162tailwind([position(static), width(auto), height(auto),
  163          padding(0), margin(0), overflow(visible),
  164          clip(auto), 'white-space'(normal)]) -->
  165    "not-sr-only", !.
  166
  167% Animation
  168
  169tailwind('transition-property'(none)) --> "tranistion-none", !.
  170tailwind(['transition-property'(Prop),
  171          'transition-timing-function'("cubic-bezier(0.4,0,0.2.1)"),
  172          'transition-duration'("150ms")]) -->
  173    "transition-",
  174    transition_property(Prop), !.
  175tailwind(['transition-property'("background-color,border-color,color,fill,stroke,opacity,box-shadow,transform"),
  176          'transition-timing-function'("cubic-bezier(0.4,0,0.2.1)"),
  177          'transition-duration'("150ms")]) -->
  178    "transition".
  179
  180transition_property("all") --> "all".
  181transition_property("background-color,border-color,color,fill-stroke") -->
  182    "colors".
  183transition_property("opacity") --> "opacity".
  184transition_property("box-shadow") --> "shadow".
  185transition_property("transform") --> "transform".
  186
  187tailwind('transition-duration'(Dur)) -->
  188    "duration-", optional(time(N), num(N)), !,
  189    { value_unit_css(N, Dur, _{zero_unit: s, number: _{unit: ms}})}.
  190
  191tailwind('transition-timing-function'(Fun)) -->
  192    "ease-", transition_timing_fn(Fun), !.
  193
  194transition_timing_fn("linear") --> "linear".
  195transition_timing_fn("cubic-bezier(0.4,0,0.2,1)") --> "in-out".
  196transition_timing_fn("cubic-bezier(0.4,0,1,1)") --> "in".
  197transition_timing_fn("cubic-bezier(0,0,0.2,1)") --> "out".
  198
  199tailwind('transition-delay'(Delay)) -->
  200    "delay-", optional(time(N), num(N)), !,
  201    { value_unit_css(N, Delay, _{zero_unit: s, number: _{unit: ms}}) }.
  202
  203tailwind('animation'("none")) --> "animate-none", !.
  204tailwind(['animation'("spin 1s linear infinite"),
  205         '@keyframes'(spin,
  206                      [from(transform("rotate(0)")),
  207                       to(transform("rotate(360deg)"))])]) -->
  208    "animate-spin", !.
  209tailwind(['animation'("ping 1s cubic-bezier(0,0,0.2,1) infinite"),
  210         '@keyframes'(ping,
  211                      ['75%, 100%'(transform("scale(2)"),
  212                                   opacity(0))])]) -->
  213    "animate-ping", !.
  214tailwind(['animation'("pulse 2s cubic-bezier(0.4,0,0.6,1) infinite"),
  215         '@keyframes'(pulse,
  216                      ['0%, 100%'(opacity(0)),
  217                       '50%'(opacity(0.5))])]) -->
  218    "animate-pulse", !.
  219tailwind(['animation'("bounce 1s infinite"),
  220         '@keyframes'(bounce,
  221                      ['0%, 100%'(transform("translateY(-25%)"),
  222                                 'animation-timing-function'("cubic-bezier(0.8,0,1,1)")),
  223                       '50%'(transform("translateY(0)"),
  224                             'animation-timing-function'("cubic-bezier(0,0,0.2,1)"))])]) -->
  225    "animate-bounce", !.
  226
  227% Background
  228
  229tailwind('background-attachment'(Attachment)) -->
  230    "bg-", one_of(["fixed", "local", "scroll"],
  231                  Attachment), !.
  232
  233tailwind('background-clip'("text")) --> "bg-clip-text", !.
  234tailwind('background-clip'(Box)) -->
  235    "bg-clip-",
  236    one_of(["border", "padding", "content"], Type), !,
  237    { format(string(Box), "~w-box", [Type]) }.
  238
  239colour_bg(special(S), S) :- !.
  240colour_bg(Colour, CssColour) :-
  241    ( has_alpha(Colour)
  242    -> Colour_ = Colour
  243    ;  colour_with_alpha(Colour, "var(--pl-bg-opacity, 1)", Colour_) ),
  244    colour_css(Colour_, CssColour).
  245
  246tailwind('background-color'(CssColour)) -->
  247    "bg-", colour(Colour), !,
  248    { colour_bg(Colour, CssColour) }.
  249
  250tailwind('--pl-bg-opacity'(Opacity)) -->
  251    "bg-opacity-", num(N), !,
  252    { value_unit_css(N, Opacity, _{value_fn: div_100}) }.
  253
  254tailwind('background-position'("right top")) --> "bg-right-top".
  255tailwind('background-position'("left top")) --> "bg-left-top".
  256tailwind('background-position'("right bottom")) --> "bg-right-bottom".
  257tailwind('background-position'("left bottom")) --> "bg-left-bottom".
  258tailwind('background-position'(Pos)) -->
  259    "bg-", one_of(["top", "center", "bottom", "left", "right"], Pos).
  260
  261background_size_length(Len) -->
  262    alternates([auto(Len),
  263               length(Len), length_unit(Len),
  264               fraction(Len), percentage(Len),
  265               num(Len)]).
  266
  267tailwind('background-size'(Size)) -->
  268    "bg-", one_of(["auto", "cover", "contain"], Size), !.
  269tailwind('background-size'(Size)) -->
  270    "bg-size-", background_size_length(Width),
  271    "-", background_size_length(Height), !,
  272    { Opts = _{zero_unit: "",
  273               number: _{unit: rem, value_fn: div_4},
  274               fraction: _{unit: "%", value_fn: mul_100}},
  275        value_unit_css(Width, WidthCss, Opts),
  276        value_unit_css(Height, HeightCss, Opts),
  277        format(string(Size), "~w ~w", [WidthCss, HeightCss]) }.
  278
  279gradient_direction("top right") --> "tr".
  280gradient_direction("top left") --> "tl".
  281gradient_direction("top") --> "t".
  282gradient_direction("left") --> "l".
  283gradient_direction("right") --> "r".
  284gradient_direction("bottom right") --> "br".
  285gradient_direction("bottom left") --> "bl".
  286gradient_direction("bottom") --> "b".
  287
  288tailwind('background-image'("none")) --> "bg-none", !.
  289tailwind('background-image'(Grad)) -->
  290    "bg-gradient-to-", gradient_direction(Dir), !,
  291    { format(string(Grad), "linear-gradient(to ~w, var(--pl-gradient-stops))",
  292             [Dir]) }.
  293
  294tailwind(['--pl-gradient-from'(ColourCss),
  295          '--pl-gradient-stops'(Stops)]) -->
  296    "from-", colour(Colour), !,
  297    { colour_css(Colour, ColourCss),
  298      as_transparent(Colour, TranspColour), colour_css(TranspColour, TranspColourCss),
  299      format(string(Stops),
  300            "var(--pl-gradient-from), var(--pl-gradient-to, ~w)",
  301            [TranspColourCss]) }.
  302
  303tailwind('--pl-gradient-to'(ColourCss)) -->
  304    "to-", colour(Colour), !,
  305    { colour_css(Colour, ColourCss) }.
  306
  307tailwind('--pl-gradient-stops'(Stops)) -->
  308    "via-", colour(Colour), !,
  309    { as_transparent(Colour, ColourTransp),
  310      colour_css(Colour, ColourCss),
  311      colour_css(ColourTransp, ColourTranspCss),
  312      format(string(Stops),
  313            "var(--pl-gradient-from),~w,var(--pl-gradient-to,~w)",
  314            [ColourCss, ColourTranspCss]) }.
  315
  316% Border
  317
  318border_radius_pos(Pos) -->
  319    "-", one_of(["tl", "tr", "t",
  320                 "bl", "br", "b",
  321                 "r", "l"],
  322                Pos_),
  323    { border_radius_pos_dirs(Pos_, Pos) }.
  324
  325border_radius_pos_dirs(t, ["top-left", "top-right"]).
  326border_radius_pos_dirs(r, ["top-right", "bottom-right"]).
  327border_radius_pos_dirs(b, ["bottom-right", "bottom-left"]).
  328border_radius_pos_dirs(l, ["bottom-left", "top-left"]).
  329border_radius_pos_dirs(tl, ["top-left"]).
  330border_radius_pos_dirs(tr, ["top-right"]).
  331border_radius_pos_dirs(br, ["bottom-right"]).
  332border_radius_pos_dirs(bl, ["bottom-left"]).
  333
  334border_radius_size("0px") --> "-none", !.
  335border_radius_size("9999px") --> "-full", !.
  336border_radius_size(Size) -->
  337    "-", border_radius_size_multi(Cls),
  338    { S is Cls * 0.25,
  339      format(string(Size), "~wrem", [S]) }.
  340
  341border_radius_size_multi(0.5) --> "sm", !.
  342border_radius_size_multi(1.5) --> "md", !.
  343border_radius_size_multi(2) --> "lg", !.
  344border_radius_size_multi(3) --> "xl", !.
  345border_radius_size_multi(4) --> "2xl", !.
  346border_radius_size_multi(6) --> "3xl".
  347
  348
  349tailwind(Style) -->
  350    "rounded",
  351    optional(border_radius_pos(Pos), { Pos = false}),
  352    optional(border_radius_size(Size), { Size = "0.25rem"}), !,
  353    { Pos == false
  354      -> Style = 'border-radius'(Size)
  355      ;   maplist({Size}/[D, St]>>(
  356                      format(atom(Attr), "border-~w-radius", [D]),
  357                      St =.. [Attr, Size]
  358                  ),
  359                  Pos,
  360                  Style) }.
  361
  362colour_border(special(S), S) :- !.
  363colour_border(Colour, CssColour) :-
  364    ( has_alpha(Colour)
  365    -> Colour_ = Colour
  366    ;  colour_with_alpha(Colour, "var(--pl-border-opacity,1)", Colour_) ),
  367    colour_css(Colour_, CssColour).
  368
  369tailwind('border-color'(ColourCss)) -->
  370    "border-", colour(Colour), !,
  371    { colour_border(Colour, ColourCss) }.
  372
  373tailwind('--pl-border-opacity'(Opacity)) -->
  374    "border-opacity-", num(Num), !,
  375    { value_unit_css(Num, Opacity, _{value_fn: div_100}) }.
  376
  377tailwind('border-style'(Style)) -->
  378    "border-", one_of(["solid", "dashed", "dotted", "double", "none"], Style), !.
  379
  380divide_width_value(WidthVal) -->
  381    "-", alternates([length(WidthVal), length_unit(WidthVal), num(WidthVal)]).
  382
  383divide_axis_styles(x, Width, ['border-right-width'(RStyle),
  384                              'border-left-width'(LStyle)]) :-
  385    format(string(RStyle), "calc(~w * var(--pl-divide-x-reverse))", [Width]),
  386    format(string(LStyle), "calc(~w * calc(1 - var(--pl-divide-x-reverse)))", [Width]).
  387divide_axis_styles(y, Width, ['border-top-width'(TStyle),
  388                              'border-bottom-width'(BStyle)]) :-
  389    format(string(TStyle), "calc(~w * var(--pl-divide-y-reverse))", [Width]),
  390    format(string(BStyle), "calc(~w * calc(1 - var(--pl-divide-y-reverse)))", [Width]).
  391
  392tailwind('&'('> * + *'(Styles))) --> % should the selector be * ~ *?
  393    "divide-", axis(axis(Axis)),
  394    optional(divide_width_value(WidthVal), { WidthVal = length(1,px) }), !,
  395    { value_unit_css(WidthVal, CssVal, _{zero_unit: "", number: _{unit: px}}),
  396      divide_axis_styles(Axis, CssVal, Styles) }.
  397
  398direction_border_attr(t, 'border-top-width').
  399direction_border_attr(r, 'border-right-width').
  400direction_border_attr(b, 'border-bottom-width').
  401direction_border_attr(l, 'border-left-width').
  402
  403colour_divide(special(S), S) :- !.
  404colour_divide(Colour, CssColour) :-
  405    ( has_alpha(Colour)
  406    -> Colour_ = Colour
  407    ;  colour_with_alpha(Colour, "var(--pl-divide-opacity,1)", Colour_) ),
  408    colour_css(Colour_, CssColour).
  409
  410tailwind('&'('> * + *'('border-color'(ColourCss)))) -->
  411    "divide-", colour(Colour), !,
  412    { colour_divide(Colour, ColourCss) }.
  413
  414tailwind('--pl-divide-opacity'(Opacity)) -->
  415    "divide-opacity-", num(Number), !,
  416    { value_unit_css(Number, Opacity, _{value_fn: div_100}) }.
  417
  418tailwind('&'('> * + *'('border-style'(Style)))) -->
  419    "divide-", one_of(["solid", "dashed", "dotted", "double", "none"], Style), !.
  420
  421ring_colour(special(Colour), Colour) :- !.
  422ring_colour(Colour, ColourCss) :-
  423    has_alpha(Colour), !,
  424    colour_css(Colour, ColourCss).
  425ring_colour(Colour, ColourCss) :-
  426    colour_with_alpha(Colour, "var(--pl-ring-opacity,1)", Colour_),
  427    colour_css(Colour_, ColourCss).
  428
  429tailwind('--pl-ring-color'(ColourCss)) -->
  430    "ring-", colour(Colour), !,
  431    { ring_colour(Colour, ColourCss) }.
  432
  433tailwind('--pl-ring-opacity'(Opacity)) -->
  434    "ring-opacity-", num(Number), !,
  435    { value_unit_css(Number, Opacity, _{value_fn: div_100}) }.
  436
  437tailwind(['--pl-ring-offset-width'(OffWidth),
  438         'box-shadow'("0 0 0 var(--pl-ring-offset-width) var(--pl-ring-offset-color), var(--pl-ring-shadow)")]) -->
  439    "ring-offset-", alternates([length(X), length_unit(X), num(X)]), !,
  440    { value_unit_css(X, OffWidth, _{zero_unit: "", number: _{unit: px}}) }.
  441
  442tailwind(['--pl-ring-offset-color'(OffColour),
  443          'box-shadow'("0 0 0 var(--pl-ring-offset-width, 0px) var(--pl-ring-offset-color), var(--pl-ring-shadow)")]) -->
  444    "ring-offset-", colour(Colour), !,
  445    { ring_colour(Colour, OffColour) }.
  446
  447tailwind('--pl-ring-inset'("inset")) --> "ring-inset", !.
  448tailwind('box-shadow'(Style)) -->
  449    "ring-", alternates([length(Len), length_unit(Len), num(Len)]), !,
  450    { value_unit_css(Len, LenCss, _{zero_unit: "", number: _{unit: px}}),
  451      format(string(Style),
  452             "var(--pl-ring-inset, ) 0 0 0 calc(~w + var(--pl-ring-offset-width,0px)) var(--pl-ring-color)",
  453            [LenCss]) }.
  454% needs to come after all the other "ring" styles
  455tailwind('box-shadow'("var(--pl-ring-inset, ) 0 0 0 calc(3px + var(--pl-ring-offset-width,0px)) var(--pl-ring-color)")) -->
  456    "ring", !.
  457
  458border_direction(Attr) -->
  459    "-", direction(direction(Dir)),
  460    { direction_border_attr(Dir, Attr) }.
  461
  462border_width_val(Width) -->
  463    "-", alternates([length(Len), length_unit(Len), num(Len)]),
  464    { value_unit_css(Len, Width, _{zero_unit: "", number: _{unit: px}}) }.
  465
  466% like optionaly, but don't cut on default
  467soft_optional(A, _) --> A, !.
  468soft_optional(_, B) --> B.
  469
  470% not doing just one with optionals, because "border-rem" & "border-r"
  471% are both valid, but hard to parse properly without leaving
  472% choice-points
  473% this also needs to come after all the other "border-" cases, since
  474% it's a prefix of all of them
  475tailwind('border-width'(Width)) -->
  476    "border", border_width_val(Width), !.
  477tailwind(Style) -->
  478    "border",
  479    soft_optional(border_direction(Attr), { Attr = 'border-width' }),
  480    soft_optional(border_width_val(Width), { Width = "1px" }), !,
  481    { Style =.. [Attr, Width] }.
  482
  483% Justify
  484
  485flex_align_style(start, "flex-start").
  486flex_align_style(end, "flex-end").
  487flex_align_style(center, "center").
  488flex_align_style(between, "space-between").
  489flex_align_style(around, "space-around").
  490flex_align_style(evenly, "space-evenly").
  491
  492tailwind('justify-content'(Justify)) -->
  493    "justify-", one_of(["start", "end", "center", "between", "around", "evenly"],
  494                      Just), !,
  495    { flex_align_style(Just, Justify) }.
  496
  497tailwind('justify-items'(Justify)) -->
  498    "justify-items-",
  499    one_of(["auto", "start" , "end", "center", "stretch"], Justify), !.
  500
  501tailwind('justify-self'(Justify)) -->
  502    "justify-self-",
  503    one_of(["auto", "start" , "end", "center", "stretch"], Justify), !.
  504
  505tailwind('align-content'(Align)) -->
  506    "content-", one_of(["start", "end", "center", "between", "around", "evenly"],
  507                       A), !,
  508    { flex_align_style(A, Align) }.
  509
  510flex_items_style(start, "flex-start") :- !.
  511flex_items_style(end, "flex-end") :- !.
  512flex_items_style(S, S).
  513
  514tailwind('align-items'(Align)) -->
  515    "items-", one_of(["start", "end", "center", "baseline", "stretch"],
  516                     A), !,
  517    { flex_items_style(A, Align) }.
  518
  519tailwind('align-self'(Align)) -->
  520    "self-", one_of(["start", "end", "center", "stretch", "auto"],
  521                     A), !,
  522    { flex_items_style(A, Align) }.
  523
  524place_content_place(between, "space-between") :- !.
  525place_content_place(around, "space-around") :- !.
  526place_content_place(evenly, "space-evenly") :- !.
  527place_content_place(P, P).
  528
  529tailwind('place-content'(Place)) -->
  530    "place-content-", one_of(["start", "end", "center", "between",
  531                             "around", "evenly", "stretch"], P), !,
  532    { place_content_place(P, Place) }.
  533
  534tailwind('place-items'(P)) -->
  535    "place-items-", one_of(["auto", "start", "end", "center", "stretch"], P).
  536
  537tailwind('place-self'(P)) -->
  538    "place-self-", one_of(["auto", "start", "end", "center", "stretch"], P).
  539
  540% effect
  541
  542shadow_size_style(sm, "0 1px 2px 0 rgba(0,0,0,0.05)").
  543shadow_size_style(md, "0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06)").
  544shadow_size_style(lg, "0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -2px rgba(0,0,0,0.05)").
  545shadow_size_style(xl, "0 20px 25px -5px rgba(0,0,0,0.1),0 10px 10px -5px rgba(0,0,0,0.04)").
  546shadow_size_style('2xl', "0 25px 50px -12px rgba(0,0,0,0.25)").
  547shadow_size_style(inner, "inset 0 2px 4px 0 rgba(0,0,0,0.06)").
  548shadow_size_style(none, "0 0 #0000").
  549
  550tailwind(['--pl-shadow'(Shadow),
  551          'box-shadow'("var(--pl-ring-offset-shadow,0 0 #0000),var(--pl-ring-shadow,0 0 #0000),var(--pl-shadow)")]) -->
  552    "shadow-",
  553    one_of(["sm", "md", "lg", "xl", "2xl", "inner", "none"],
  554           Val), !,
  555    { shadow_size_style(Val, Shadow) }.
  556tailwind(['--pl-shadow'("0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px 0 rgba(0,0,0,0.06)"),
  557          'box-shadow'("var(--pl-ring-offset-shadow,0 0 #0000),var(--pl-ring-shadow,0 0 #0000),var(--pl-shadow)")]) -->
  558    "shadow", !.
  559
  560tailwind(opacity(Opacity)) -->
  561    "opacity-", num(Num), !,
  562    { value_unit_css(Num, Opacity, _{value_fn: div_100}) }.
  563
  564% grid
  565
  566tailwind('grid-template-columns'(none)) --> "grid-cols-none", !.
  567tailwind('grid-template-columns'(none)) --> "grid-cols-0", !.
  568tailwind('grid-template-columns'(Style)) -->
  569    "grid-cols-", integer(Int), !,
  570    { format(string(Style), "repeat(~d, minmax(0, 1fr))", [Int]) }.
  571
  572tailwind('grid-column'(auto)) --> "col-auto", !.
  573
  574tailwind('grid-column'("-1 / 1")) --> "col-span-full", !.
  575tailwind('grid-column'(Span)) -->
  576    "col-span-", integer(Int), !,
  577    { format(string(Span), "span ~d / span ~d", [Int, Int]) }.
  578
  579tailwind('grid-column-start'("auto")) --> "col-start-auto", !.
  580tailwind('grid-column-start'(Start)) -->
  581    "col-start-", integer(Start), !.
  582
  583tailwind('grid-template-rows'("none")) --> "grid-rows-none", !.
  584tailwind('grid-template-rows'("none")) --> "grid-rows-0", !.
  585tailwind('grid-template-rows'(Rows)) -->
  586    "grid-rows-", integer(Int), !,
  587    { format(string(Rows), "repeat(~d, minmax(0, 1fr))", [Int]) }.
  588
  589tailwind('grid-row'("auto")) --> "row-auto", !.
  590
  591tailwind('grid-row'("-1 / 1")) --> "row-span-full", !.
  592tailwind('grid-row'(Span)) -->
  593    "row-span-", integer(Int), !,
  594    { format(string(Span), "span ~d / span ~d", [Int, Int]) }.
  595
  596tailwind('grid-row-start'("auto")) --> "row-start-auto", !.
  597tailwind('grid-row-start'(Start)) --> "row-start-", integer(Start), !.
  598
  599grid_flow_style(row, "row").
  600grid_flow_style(col, "col").
  601grid_flow_style('row-dense', "row dense").
  602grid_flow_style('col-dense', "col dense").
  603
  604tailwind('grid-auto-flow'(Flow)) -->
  605    "grid-flow-", one_of(["row-dense", "col-dense", "row", "col"],
  606                         FlowType), !,
  607    { grid_flow_style(FlowType, Flow) }.
  608
  609grid_auto_style(auto, "auto").
  610grid_auto_style(min, "min-content").
  611grid_auto_style(max, "max-content").
  612grid_auto_style(fr, "minmax(0, 1fr)").
  613
  614tailwind('grid-auto-columns'(Cols)) -->
  615    "auto-cols-", one_of(["auto", "min", "max", "fr"],
  616                        AutoType), !,
  617    { grid_auto_style(AutoType, Cols) }.
  618
  619tailwind('grid-auto-rows'(Rows)) -->
  620    "auto-rows-", one_of(["auto", "min", "max", "fr"],
  621                         AutoType), !,
  622    { grid_auto_style(AutoType, Rows) }.
  623
  624gap_type(Val) -->
  625    alternates([percentage(V), length(V), length_unit(V), num(V)]),
  626    { value_unit_css(V, Val, _{zero_unit: "", number: _{unit: rem,
  627                                                        value_fn: div_4}}) }.
  628
  629tailwind('column-gap'(Val)) -->
  630    "gap-x-", gap_type(Val), !.
  631tailwind('row-gap'(Val)) -->
  632    "gap-y-", gap_type(Val), !.
  633tailwind('gap'(Val)) -->
  634    "gap-", gap_type(Val), !.
  635
  636% interactivity
  637
  638tailwind(appearance(none)) --> "appearance-none", !.
  639
  640tailwind(cursor(Type)) -->
  641    "cursor-", one_of(["auto", "default", "pointer", "wait", "text", "move",
  642                       "not-allowed"], Type), !.
  643
  644outline_type(none, "2px solid transparent").
  645outline_type(white, "2px dotted white").
  646outline_type(black, "2px dotted black").
  647
  648tailwind(['outline-offset'("2px"), outline(Type)]) -->
  649    "outline-", one_of(["none", "white", "black"], Type_), !,
  650    { outline_type(Type_, Type) }.
  651
  652tailwind('pointer-events'(Events)) -->
  653    "pointer-events-", one_of(["none", "auto"], Events), !.
  654
  655resize_type(none, "none").
  656resize_type(x, "horizontal").
  657resize_type(y, "vertical").
  658
  659tailwind(resize(Resize)) -->
  660    "resize-", one_of(["none", "x", "y"], Type), !,
  661    { resize_type(Type, Resize) }.
  662tailwind(resize("both")) --> "resize", !.
  663
  664tailwind('user-select'(Type)) -->
  665    "select-", one_of(["none", "text", "all", "auto"], Type), !.
  666
  667% layout
  668
  669% [XXX]: if there's a media query min-width, then this should be
  670% {:max-width (breakpoint->pixels media-query-min-width)}
  671% but...right now don't have that exposed.
  672tailwind(width("100%")) --> "container", !.
  673
  674tailwind('box-sizing'("border-box")) --> "box-border", !.
  675tailwind('box-sizing'("content-box")) --> "box-content", !.
  676
  677tailwind(display("none")) --> "hidden", !.
  678tailwind(display(Display)) -->
  679    one_of(["block", "inline-block", "flex", "inline-flex",
  680            "inline-grid", "inline", "grid", "table-column-group",
  681            "table-footer-group", "table-header-group",
  682            "table-row-group",
  683            "table-column", "table-cell", "table-row", "table-caption",
  684            "table", "contents", "hidden", "flow-root"
  685           ], Display), !.
  686
  687tailwind(float(Dir)) --> "float-", one_of(["left", "right", "none"], Dir), !.
  688
  689tailwind(clear(Dir)) -->
  690    "clear-", one_of(["left", "right", "both", "none"], Dir), !.
  691
  692tailwind('object-fit'(Fit)) -->
  693    "object-", one_of(["contain", "cover", "fill", "none", "scale-down"], Fit),
  694    !.
  695
  696tailwind(Style) -->
  697    "overflow-", axis(axis(Axis)), "-",
  698    one_of(["auto", "hidden", "visible", "scroll"], Mode), !,
  699    { format(atom(Prop), "overflow-~w", [Axis]),
  700      Style =.. [Prop, Mode] }.
  701tailwind(overflow(Mode)) -->
  702    "overflow-", one_of(["auto", "hidden", "visible", "scroll"], Mode), !.
  703
  704tailwind(Style) -->
  705    "overscroll-", axis(axis(Axis)), "-",
  706    one_of(["auto", "contain", "none"], Mode), !,
  707    { format(atom(Prop), "overscroll-~w", [Axis]),
  708      Style =.. [Prop, Mode] }.
  709tailwind(overscroll(Mode)) -->
  710    "overscroll-", one_of(["auto", "contain", "none"], Mode), !.
  711
  712tailwind(position(Pos)) -->
  713    one_of(["static", "fixed", "absolute", "relative", "sticky"], Pos), !.
  714
  715tailwind(Styles) -->
  716    soft_optional(signus(S), { S = '+' }),
  717    one_of(["top", "right", "bottom", "left",
  718            "inset-x", "inset-y", "inset"], Mode),
  719    "-",
  720    alternates([length(Val), length_unit(Val), fraction(Val),
  721               percentage(Val), full_100(Val), auto(Val),
  722               num(Val)]), !,
  723    { value_unit_css(Val, Value, _{signus: S,
  724                                   zero_unit: "",
  725                                   number: _{unit: rem, value_fn: div_4},
  726                                   fraction: _{unit: "%", value_fn: mul_100}}),
  727      positioning_mode_dirs(Mode, Dirs),
  728      maplist({Value}/[Dir, Style]>>(Style =.. [Dir, Value]),
  729             Dirs, Styles) }.
  730
  731positioning_mode_dirs(inset, [top, right, bottom, left]) :- !.
  732positioning_mode_dirs('inset-x', [right, left]) :- !.
  733positioning_mode_dirs('inset-y', [top, bottom]) :- !.
  734positioning_mode_dirs(Dir, [Dir]).
  735
  736tailwind(visibility(Vis)) -->
  737    one_of(["visible", "invisible"], Vis0), !,
  738    { Vis0 == invisible -> Vis = hidden ; Vis = Vis0 }.
  739
  740tailwind('z-index'("auto")) --> "z-auto", !.
  741tailwind('z-index'(Index)) --> "z-", integer(Index), !.
  742
  743% Sizing
  744
  745tailwind(width(Width)) -->
  746    "w-", alternates([length(Val), length_unit(Val), fraction(Val),
  747                      percentage(Val), full_100(Val), auto(Val),
  748                      screen_100vw(Val), min_content(Val), max_content(Val),
  749                      num(Val)]), !,
  750    { value_unit_css(Val, Width, _{zero_unit: "",
  751                                   number: _{unit: rem, value_fn: div_4},
  752                                   fraction: _{unit: '%', value_fn: mul_100}}) }.
  753
  754tailwind('min-width'(Width)) -->
  755    "min-w-", alternates([length(Val), length_unit(Val), fraction(Val),
  756                      percentage(Val), full_100(Val), auto(Val),
  757                      screen_100vw(Val), min_content(Val), max_content(Val),
  758                      num(Val)]), !,
  759    { value_unit_css(Val, Width, _{zero_unit: "",
  760                                   number: _{unit: rem, value_fn: div_4},
  761                                   fraction: _{unit: '%', value_fn: mul_100}}) }.
  762
  763tailwind('max-width'(Width)) -->
  764    "max-w-", alternates([length(Val), length_unit(Val), fraction(Val),
  765                          percentage(Val), full_100(Val), unset(Val),
  766                          screen_100vw(Val), min_content(Val), max_content(Val),
  767                          num(Val)]), !,
  768    { value_unit_css(Val, Width, _{zero_unit: "",
  769                                   number: _{unit: rem, value_fn: div_4},
  770                                   fraction: _{unit: '%', value_fn: mul_100}}) }.
  771
  772tailwind(height(Height)) -->
  773    "h-", alternates([length(Val), length_unit(Val), fraction(Val),
  774                      percentage(Val), full_100(Val), auto(Val),
  775                      screen_100vw(Val), min_content(Val), max_content(Val),
  776                      num(Val)]), !,
  777    { value_unit_css(Val, Height, _{zero_unit: "",
  778                                   number: _{unit: rem, value_fn: div_4},
  779                                   fraction: _{unit: '%', value_fn: mul_100}}) }.
  780
  781tailwind('min-height'(Height)) -->
  782    "min-h-", alternates([length(Val), length_unit(Val), fraction(Val),
  783                      percentage(Val), full_100(Val), auto(Val),
  784                      screen_100vw(Val), min_content(Val), max_content(Val),
  785                      num(Val)]), !,
  786    { value_unit_css(Val, Height, _{zero_unit: "",
  787                                   number: _{unit: rem, value_fn: div_4},
  788                                   fraction: _{unit: '%', value_fn: mul_100}}) }.
  789
  790tailwind('max-height'(Height)) -->
  791    "max-h-", alternates([length(Val), length_unit(Val), fraction(Val),
  792                          percentage(Val), full_100(Val), unset(Val),
  793                          screen_100vw(Val), min_content(Val), max_content(Val),
  794                          num(Val)]), !,
  795    { value_unit_css(Val, Height, _{zero_unit: "",
  796                                   number: _{unit: rem, value_fn: div_4},
  797                                   fraction: _{unit: '%', value_fn: mul_100}}) }.
  798
  799% spacing
  800
  801dir_axis_props(axis(x), [left, right]).
  802dir_axis_props(axis(y), [top, bottom]).
  803dir_axis_props(direction(t), [top]).
  804dir_axis_props(direction(r), [right]).
  805dir_axis_props(direction(b), [bottom]).
  806dir_axis_props(direction(l), [left]).
  807
  808tailwind(padding(Padding)) -->
  809    soft_optional(signus(S), { S = '+' }),
  810    "p-", alternates([length(Val), length_unit(Val), num(Val)]), !,
  811    { value_unit_css(Val, Padding, _{signus: S, zero_unit: "",
  812                                    number: _{unit: rem, value_fn: div_4}}) }.
  813tailwind(Styles) -->
  814    soft_optional(signus(S), { S = '+' }),
  815    "p", alternates([axis(A), direction(A)]), "-",
  816    alternates([length(Val), length_unit(Val), num(Val)]), !,
  817    { value_unit_css(Val, Padding, _{signus: S, zero_unit: "",
  818                                    number: _{unit: rem, value_fn: div_4}}),
  819      dir_axis_props(A, Props),
  820      maplist({Padding}/[Prop, Style]>>(
  821                  format(atom(Attr), "padding-~w", [Prop]),
  822                  Style =.. [Attr, Padding]),
  823             Props, Styles) }.
  824
  825tailwind(margin(Margin)) -->
  826    soft_optional(signus(S), { S = '+' }),
  827    "m-", alternates([length(Val), length_unit(Val), num(Val)]), !,
  828    { value_unit_css(Val, Margin, _{signus: S, zero_unit: "",
  829                                    number: _{unit: rem, value_fn: div_4}}) }.
  830tailwind(Styles) -->
  831    soft_optional(signus(S), { S = '+' }),
  832    "m", alternates([axis(A), direction(A)]), "-",
  833    alternates([length(Val), length_unit(Val), num(Val)]), !,
  834    { value_unit_css(Val, Margin, _{signus: S, zero_unit: "",
  835                                    number: _{unit: rem, value_fn: div_4}}),
  836      dir_axis_props(A, Props),
  837      maplist({Margin}/[Prop, Style]>>(
  838                  format(atom(Attr), "margin-~w", [Prop]),
  839                  Style =.. [Attr, Margin]),
  840             Props, Styles) }.
  841
  842space_between_reverse(true) --> "-reverse".
  843space_between_reverse(false) --> [].
  844
  845space_between_prop(x, false, left).
  846space_between_prop(x, true, right).
  847space_between_prop(y, false, top).
  848space_between_prop(y, true, bottom).
  849
  850tailwind('&'('> * + *'(Style))) -->
  851    soft_optional(signus(S), { S = '+' }),
  852    "space-", axis(axis(A)), "-",
  853    alternates([length(Val), length_unit(Val), num(Val)]),
  854    space_between_reverse(Rev), !,
  855    { space_between_prop(A, Rev, Dir), !,
  856      format(atom(Prop), "margin-~w", [Dir]),
  857      value_unit_css(Val, Value, _{signus: S, zero_unit: "",
  858                                   number: _{unit: rem, value_fn: div_4}}),
  859      Style =.. [Prop, Value] }.
  860
  861% svg
  862
  863tailwind(fill(ColourCss)) -->
  864    "fill-", colour(Colour), !,
  865    { colour_css(Colour, ColourCss) }.
  866
  867tailwind(stroke(ColourCss)) -->
  868    "stroke-", colour(Colour), !,
  869    { colour_css(Colour, ColourCss) }.
  870
  871tailwind('stroke-width'(Thickness)) -->
  872    "stroke-", num(number(Thickness)), !.
  873
  874% table
  875
  876tailwind('border-collapse'(Type)) -->
  877    "border-", one_of(["collapse", "separate"], Type), !.
  878
  879tailwind('table-layout'(Type)) -->
  880    "table-", one_of(["auto", "fixed"], Type), !.
  881
  882% transform
  883
  884tailwind(transform(none)) --> "transform-none", !.
  885tailwind(['--pl-translate-x'(0),
  886         '--pl-translate-y'(0),
  887         '--pl-rotate'(0),
  888         '--pl-skew-x'(0),
  889         '--pl-skew-y'(0),
  890         '--pl-scale-x'(0),
  891         '--pl-scale-y'(0),
  892         transform("translate3d(var(--pl-translate-x),var(--pl-translate-y),0) rotate(var(--pl-rotate)) skewX(var(--pl-skew-x)) skewY(var(--pl-skew-y)) scaleX(var(--pl-scale-x)) scaleY(var(--pl-scale-y))")
  893         ]) -->
  894    "transform-gpu", !.
  895tailwind(['--pl-translate-x'(0),
  896         '--pl-translate-y'(0),
  897         '--pl-rotate'(0),
  898         '--pl-skew-x'(0),
  899         '--pl-skew-y'(0),
  900         '--pl-scale-x'(0),
  901         '--pl-scale-y'(0),
  902         transform("translateX(var(--pl-translate-x)) translateY(var(--pl-translate-y)) rotate(var(--pl-rotate)) skewX(var(--pl-skew-x)) skewY(var(--pl-skew-y)) scaleX(var(--pl-scale-x)) scaleY(var(--pl-scale-y))")
  903         ]) -->
  904    "transform", !.
  905
  906transform_origin_pos('top-left', "top left") :- !.
  907transform_origin_pos('top-right', "top right") :- !.
  908transform_origin_pos('bottom-left', "bottom left") :- !.
  909transform_origin_pos('bottom-right', "bottom right") :- !.
  910transform_origin_pos(P, P).
  911
  912tailwind('transform-origin'(Origin)) -->
  913    "origin-", one_of(["top-left", "top-right", "top",
  914                      "bottom-left", "bottom-right", "bottom",
  915                      "left", "center", "right"],
  916                     Orig), !,
  917    { transform_origin_pos(Orig, Origin) }.
  918
  919tailwind(Style) -->
  920    soft_optional(signus(S), { S = '+' }),
  921    "scale-", axis(axis(A)), "-", num(Val), !,
  922    { value_unit_css(Val, CssVal, _{signus: S, value_fn: div_100}),
  923      format(atom(Prop), "--pl-scale-~w", [A]),
  924      Style =.. [Prop, CssVal] }.
  925tailwind(['--pl-scale-x'(CssVal), '--pl-scale-y'(CssVal)]) -->
  926    soft_optional(signus(S), { S = '+' }),
  927    "scale-", num(Val), !,
  928    { value_unit_css(Val, CssVal, _{signus: S, value_fn: div_100}) }.
  929
  930tailwind('--pl-rotate'(Rotate)) -->
  931    soft_optional(signus(S), { S = '+' }),
  932    "rotate-", alternates([angle(V), num(V)]), !,
  933    { value_unit_css(V, Rotate, _{signus: S, zero_unit: "",
  934                                  number: _{unit: deg}}) }.
  935
  936tailwind(Style) -->
  937    soft_optional(signus(S), { S = '+' }),
  938    "translate-", axis(axis(A)), "-",
  939    alternates([length(V), length_unit(V), fraction(V),
  940                percentage(V), full_100(V), num(V)]), !,
  941    { format(atom(Prop), "--pl-translate-~w", [A]),
  942      value_unit_css(V, Val, _{signus: S, zero_unit: "",
  943                               number: _{unit: rem, value_fn: div_4},
  944                               fraction: _{unit: "%", value_fn: mul_100}}),
  945      Style =.. [Prop, Val] }.
  946
  947tailwind(Style) -->
  948    soft_optional(signus(S), { S = '+' }),
  949    "skew-", axis(axis(A)), "-", alternates([angle(V), num(V)]),
  950    { value_unit_css(V, Skew, _{signus: S, zero_unit: "",
  951                                number: _{unit: deg}}),
  952      format(atom(Prop), "--pl-skew-~w", [A]),
  953      Style =.. [Prop, Skew] }.
  954
  955% typography
  956
  957default_font_family(sans, "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"").
  958default_font_family(serif, "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif").
  959default_font_family(mono, "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace").
  960
  961tailwind('font-family'(FontFamily)) -->
  962    "font-", one_of(["sans", "serif", "mono"], Family), !,
  963    { default_font_family(Family, FontFamily) }.
  964
  965tailwind(['font-size'(SizeRem), 'line-height'(HeightRem)]) -->
  966    "text-", one_of(["xs", "sm", "base", "lg", "xl", "2xl", "3xl",
  967                     "4xl", "5xl", "6xl", "7xl", "8xl", "9xl"],
  968                   SzCls), !,
  969    { font_class_size_height(SzCls, Size, Height),
  970      format(string(SizeRem), "~wrem", [Size]),
  971      format(string(HeightRem), "~wrem", [Height]) }.
  972
  973font_class_size_height(xs, 0.75, 1).
  974font_class_size_height(sm, 0.875, 1.25).
  975font_class_size_height(base, 1, 1.5).
  976font_class_size_height(lg, 1.125, 1.75).
  977font_class_size_height(xl, 1.25, 1.75).
  978font_class_size_height('2xl', 1.5, 2).
  979font_class_size_height('3xl', 1.875, 2.25).
  980font_class_size_height('4xl', 2.25, 2.5).
  981font_class_size_height('5xl', 3, 1).
  982font_class_size_height('6xl', 3.75, 1).
  983font_class_size_height('7xl', 4.5, 1).
  984font_class_size_height('8xl', 6, 1).
  985font_class_size_height('9xl', 8, 1).
  986
  987% non-tailwind extension, giroutte-style
  988tailwind('font-size'(Size)) -->
  989    "font-size-", alternates([length(V), fraction(V), percentage(V), num(V)]), !,
  990    { value_unit_css(V, Size, _{number: _{unit: rem, value_fn: div_4},
  991                                fraction: _{unit: '%', value_fn: mul_100}}) }.
  992
  993tailwind(['-webkit-font-smoothing'("antialiased"),
  994         '-moz-osx-font-smoothing'("grayscale")]) -->
  995    "antialiased", !.
  996tailwind(['-webkit-font-smoothing'("auto"),
  997          '-moz-osx-font-smoothing'("auto")]) -->
  998    "subpixel-antialiased", !.
  999
 1000tailwind('font-style'("italic")) --> "italic", !.
 1001tailwind('font-style'("normal")) --> "not-italic", !.
 1002
 1003tailwind('font-weight'(Weight)) -->
 1004    "font-", one_of(["thin", "extralight", "light", "normal",
 1005                     "medium", "semibold", "bold", "extrabold", "black"],
 1006                    WeightType), !,
 1007    { font_type_weight(WeightType, Weight) }.
 1008
 1009font_type_weight(thin, 100).
 1010font_type_weight(extralight, 200).
 1011font_type_weight(light, 300).
 1012font_type_weight(normal, 400).
 1013font_type_weight(medium, 500).
 1014font_type_weight(semibold, 600).
 1015font_type_weight(bold, 700).
 1016font_type_weight(extrabold, 800).
 1017font_type_weight(black, 900).
 1018
 1019tailwind('font-variant-numeric'(Variant)) -->
 1020    one_of(["normal-nums", "ordinal", "slashed-zero",
 1021           "lining-nums", "oldstyle-nums", "proportional-nums",
 1022           "tabular-nums", "diagonal-fractions", "stacked-fractions"],
 1023          Variant), !.
 1024
 1025tailwind('letter-spacing'(SpacingEm)) -->
 1026    "tracking-", one_of(["tighter", "tight", "normal",
 1027                         "wider", "widest", "wide"], Size), !,
 1028    { letter_spacing(Size, Spacing),
 1029      format(string(SpacingEm), "~wem", [Spacing]) }.
 1030
 1031letter_spacing(tighter, -0.05).
 1032letter_spacing(tight, -0.025).
 1033letter_spacing(normal, 0).
 1034letter_spacing(wide, 0.025).
 1035letter_spacing(wider, 0.05).
 1036letter_spacing(widest, 0.1).
 1037
 1038tailwind('line-height'(LineHeight)) -->
 1039    "leading-", one_of(["none", "tight", "snug", "normal",
 1040                        "relaxed", "loose"], SizeName), !,
 1041    { line_height_size(SizeName, LineHeight) }.
 1042tailwind('line-height'(LineHeight)) -->
 1043    "leading-", num(Size), !,
 1044    { value_unit_css(Size, LineHeight, _{zero_unit: "",
 1045                                        number: _{unit: rem,
 1046                                                  value_fn: div_4}}) }.
 1047
 1048
 1049line_height_size(none, 1).
 1050line_height_size(tight, 1.25).
 1051line_height_size(snug, 1.375).
 1052line_height_size(normal, 1.5).
 1053line_height_size(relaxed, 1.625).
 1054line_height_size(loose, 2).
 1055
 1056% non-tailwind extension, giroutte-style
 1057tailwind('line-height'(Height)) -->
 1058    "line-height-",
 1059    alternates([length(V), fraction(V), percentage(V), num(V)]), !,
 1060    { value_unit_css(V, Height, _{fraction: _{unit: '%', value_fn: mul_100}}) }.
 1061
 1062tailwind('list-style-type'(Type)) -->
 1063    "list-", one_of(["none", "disc", "decimal"], Type), !.
 1064
 1065tailwind('list-style-position'(Type)) -->
 1066    "list-", one_of(["inside", "outside"], Type), !.
 1067
 1068tailwind('&'('::placeholder'(Style))) -->
 1069    "placeholder-", colour(Colour), !,
 1070    { colour_placeholder(Colour, Style) }.
 1071
 1072colour_placeholder(special(S), S) :- !.
 1073colour_placeholder(Colour, CssColour) :-
 1074    ( has_alpha(Colour)
 1075    -> Colour_ = Colour
 1076    ;  colour_with_alpha(Colour, "var(--pl-placeholder-opacity,1)", Colour_) ),
 1077    colour_css(Colour_, CssColour).
 1078
 1079tailwind('--pl-placeholder-opacity'(Opacity)) -->
 1080    "placeholder-opacity-", num(Val), !,
 1081    { value_unit_css(Val, Opacity, _{value_fn: div_100}) }.
 1082
 1083tailwind('text-align'(Align)) -->
 1084    "text-", one_of(["left", "center", "right", "justify"], Align), !.
 1085
 1086tailwind(color(ColourCss)) -->
 1087    "text-", colour(Colour), !,
 1088    { colour_text(Colour, ColourCss) }.
 1089
 1090colour_text(special(S), S) :- !.
 1091colour_text(Colour, CssColour) :-
 1092    ( has_alpha(Colour)
 1093    -> Colour_ = Colour
 1094    ;  colour_with_alpha(Colour, "var(--pl-text-opacity,1)", Colour_) ),
 1095    colour_css(Colour_, CssColour).
 1096
 1097tailwind('--pl-text-opacity'(Opacity)) -->
 1098    "text-opacity-", num(Num), !,
 1099    { value_unit_css(Num, Opacity, _{value_fn: div_100}) }.
 1100
 1101tailwind('text-decoration'("none")) --> "no-underline", !.
 1102tailwind('text-decoration'(Decoration)) -->
 1103    one_of(["underline", "line-through"], Decoration), !.
 1104
 1105tailwind('text-transform'("none")) --> "normal-case", !.
 1106tailwind('text-transform'(Trans)) -->
 1107    one_of(["uppercase", "lowercase", "capitalize"], Trans), !.
 1108
 1109tailwind([overflow(hidden),
 1110          'text-overflow'(ellipsis),
 1111          'white-space'(nowrap)]) -->
 1112    "truncate", !.
 1113tailwind('text-overflow'(ellipsis)) --> "overflow-ellipsis", !.
 1114tailwind('text-overflow'(clip)) --> "overflow-clip", !.
 1115
 1116tailwind('vertical-align'(Align)) -->
 1117    "align-", one_of(["baseline", "top", "middle", "bottom",
 1118                      "text-top", "text-bottom"], Align), !.
 1119
 1120tailwind('white-space'(Control)) -->
 1121    "whitespace-", one_of(["normal", "nowrap", "pre-line", "pre-wrap",
 1122                          "pre"], Control), !.
 1123
 1124tailwind(['overflow-wrap'(normal), 'word-break'(normal)]) -->
 1125    "break-normal", !.
 1126tailwind('overflow-wrap'("break-word")) --> "break-words", !.
 1127tailwind('word-break'("break-all")) --> "break-all", !